This library controls the WNC. There is a derived class for usage from the K64F board.

Fork of WncControllerLibrary by Fred Kellerman

Files at this revision

API Documentation at this revision

Comitter:
jmf
Date:
Thu Apr 06 21:42:30 2017 -0400
Parent:
35:7c9d0f29ff7a
Child:
37:92acf8c20e6d
Commit message:
jdfjadfjadlkjfasd;lkj

Changed in this revision

WncController.cpp Show annotated file Show diff for this revision Revisions of this file
WncController.h Show annotated file Show diff for this revision Revisions of this file
--- a/WncController.cpp	Thu Mar 09 00:58:50 2017 +0000
+++ b/WncController.cpp	Thu Apr 06 21:42:30 2017 -0400
@@ -1,2127 +1,2128 @@
-/*
-    Copyright (c) 2016 Fred Kellerman
- 
-    Permission is hereby granted, free of charge, to any person obtaining a copy
-    of this software and associated documentation files (the "Software"), to deal
-    in the Software without restriction, including without limitation the rights
-    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-    copies of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
- 
-    The above copyright notice and this permission notice shall be included in
-    all copies or substantial portions of the Software.
- 
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-    THE SOFTWARE.
-    
-    @file          WncController.cpp
-    @purpose       Controls WNC 14A2A Cellular Modem
-    @version       1.0
-    @date          July 2016
-    @author        Fred Kellerman
-    
-    
-    An Example of usage:
-    
-    WncControllerK64F mdm(&wncPinList, &mdmUart, &debugUart);
-
-    mdm.enableDebug(true, true);
-
-    if (false == mdm.powerWncOn("m2m.com.attz", 60)) {
-        while(1);
-    }
-
-    // ICCID and MSISDN
-    string iccid; string msisdn;
-    if (mdm.getICCID(&iccid) == true) {
-        if (mdm.convertICCIDtoMSISDN(iccid, &msisdn) == true) {
-           // Send an SMS message (must use 15-digit MISDN number!)
-           mdm.sendSMSText(msisdn.c_str(), "Hello from WNC Kit -> from WNC");
-        }
-    }
-
-    // Get an IP address setup for the socket #1 (0 indexed))
-    if (true == mdm.resolveUrl(0, "www.att.com"))
-    {
-        // Report server IP
-        if (true == mdm.getIpAddr(0, ipAddrStr)) {
-            debugUart.puts("Server IP: ");
-            debugUart.puts(ipAddrStr);
-            debugUart.puts("\r\n");
-        }
-
-        // Open Socket #1, TCP=true resolved IP on port 80:
-        if (true == mdm.openSocket(0, 80, true)) {
-            // Write some data
-            const uint8_t * dataStr = "GET /index.html HTTP/1.0\r\nFrom: someuser@someuser.com\r\nUser-Agent: HTTPTool/1.0\r\n\r\n";
-            if (true == mdm.write(0, dataStr, strlen((const char *)dataStr)))
-            {
-                const uint8_t * myBuf;
-                mdm.setReadRetries(0, 20);
-                uint32_t n = mdm.read(0, &myBuf);
-                if (n > 0)
-                    debugUart.printf("Read %d chars: %s\r\n", n, myBuf);
-                else
-                    debugUart.puts("No read data!\r\n");
-            }
-        }
-    }
-    
-*/
-
-
-#include <cstdlib>
-#include <cctype>
-#include <string.h>
-#include "WncController.h"
-
-namespace WncController_fk {
-
-/////////////////////////////////////////////////////
-// Static initializers
-/////////////////////////////////////////////////////
-WncController::WncSocketInfo_s WncController::m_sSock[MAX_NUM_WNC_SOCKETS];
-const WncController::WncSocketInfo_s WncController::defaultSockStruct = { 0, false, "192.168.0.1", 80, 0, 25, true, 30 };
-
-WncController::WncState_e WncController::m_sState = WNC_OFF;
-uint16_t WncController::m_sCmdTimeoutMs = WNC_CMD_TIMEOUT_MS;
-string WncController::m_sApnStr = "NULL";
-string WncController::m_sWncStr;
-uint8_t WncController::m_sPowerUpTimeoutSecs = MAX_POWERUP_TIMEOUT;
-bool WncController::m_sDebugEnabled = false;
-bool WncController::m_sMoreDebugEnabled = false;
-bool WncController::m_sCheckNetStatus = false;   // Turn on internet status check between every command
-const char * const WncController::INVALID_IP_STR = "";
-bool WncController::m_sReadyForSMS = false;
-
-
-/**
- * C++ version 0.4 char* style "itoa":
- * Written by Lukás Chmela
- * Released under GPLv3.
- */
-static char* itoa(int64_t value, char* result, int base)
-{
-    // check that the base is valid
-    if ( base < 2 || base > 36 ) {
-        *result = '\0';
-        return result;
-    }
-
-    char* ptr = result, *ptr1 = result, tmp_char;
-    int64_t tmp_value;
-
-    do {
-        tmp_value = value;
-        value /= base;
-        *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)];
-    } while ( value );
-
-    // Apply negative sign
-    if ( tmp_value < 0 )
-        *ptr++ = '-';
-
-    *ptr-- = '\0';
-
-    while ( ptr1 < ptr ) {
-        tmp_char = *ptr;
-        *ptr-- = *ptr1;
-        *ptr1++ = tmp_char;
-    }
-
-    return result;
-}
-
-const char * WncController::_to_string(int64_t value)
-{
-    static char str[21];  // room for signed 64-bit + null
-    itoa(value, str, 10);
-    return (str);
-}
-
-const char * WncController::_to_hex_string(uint8_t value)
-{
-    static char str[3];   // room for 8-bit + null
-    itoa(value, str, 16);
-    return (str);
-}
-
-WncController::WncController(void)
-{
-    for(unsigned i; i<MAX_NUM_WNC_SOCKETS; i++)
-        m_sSock[i] = defaultSockStruct;
-}
-
-void WncController::enableDebug(bool on, bool moreDebugOn)
-{
-    m_sDebugEnabled = on;
-    m_sMoreDebugEnabled = moreDebugOn;
-}
-
-WncController::WncState_e WncController::getWncStatus(void)
-{
-    return (m_sState);
-}
-
-int16_t WncController::getDbmRssi(void)
-{
-    int16_t rssi, ber;
-    if (at_getrssiber_wnc(&rssi, &ber) == true)
-        return (rssi);
-    else
-        return (99);
-}
-
-int16_t WncController::get3gBer(void)
-{
-    int16_t rssi, ber;
-    if (at_getrssiber_wnc(&rssi, &ber) == true)
-        return (ber);
-    else
-        return (99);
-}
-
-bool WncController::powerWncOn(const char * const apn, uint8_t powerUpTimeoutSecs)
-{
-    dbgPuts("Waiting for WNC to Initialize...");
-    m_sPowerUpTimeoutSecs = powerUpTimeoutSecs;
-    m_sState = WNC_ON_NO_CELL_LINK;  // Turn soft on to allow "AT" for init to be sent!
-    if (initWncModem(powerUpTimeoutSecs) == true) {
-        // Set the Apn
-        setApnName(apn);
-        if (false == softwareInitMdm()) {
-            dbgPuts("Software init failed!");
-            m_sState = WNC_OFF;
-        }
-    }
-    else {
-        dbgPuts("Power up failed!");
-        m_sState = WNC_OFF;
-    }
-    
-    return ((m_sState == WNC_ON) || (m_sState == WNC_ON_NO_CELL_LINK));
-}
-
-size_t WncController::sendCustomCmd(const char * cmd, char * resp, size_t sizeRespBuf, int ms_timeout)
-{
-    string * respStr;
-    
-    if (sizeRespBuf > 0) {
-        AtCmdErr_e r = at_send_wnc_cmd(cmd, &respStr, ms_timeout);
-        strncpy(resp, respStr->c_str(), sizeRespBuf);
-        if (respStr->size() > sizeRespBuf)
-            dbgPuts("sendCustomCmd truncated!");
-            
-        return (respStr->size());
-    }
-    
-    dbgPuts("sendCustomCmd: would have overrun!");
-    
-    return (0);
-}
-
-bool WncController::pingUrl(const char * url)
-{
-    string ipAddr;
-    
-    if (true == at_dnsresolve_wnc(url, &ipAddr))
-        return (pingIp(ipAddr.c_str()));
-    else
-        dbgPuts("pingUrl DNS resolve: failed!");
-        
-    return (false);
-}
-
-bool WncController::pingIp(const char * ip)
-{
-    if (true == at_ping_wnc(ip))
-        return (true);
-    else
-        dbgPuts("pingIp: failed!");
-    
-    return (false);
-}
-
-bool WncController::getWncNetworkingStats(WncIpStats * s)
-{
-    return (at_get_wnc_net_stats(s));
-}
-
-bool WncController::getIpAddr(uint16_t numSock, char myIpAddr[MAX_LEN_IP_STR])
-{
-    if (numSock < MAX_NUM_WNC_SOCKETS) {
-        strncpy(myIpAddr, m_sSock[numSock].myIpAddressStr.c_str(), MAX_LEN_IP_STR);
-        myIpAddr[MAX_LEN_IP_STR - 1] = '\0';
-        return (true);
-    }
-    else {
-        myIpAddr[0] = '\0';
-        return (false);
-    }
-}
-
-bool WncController::setApnName(const char * const apnStr)
-{
-    if (at_setapn_wnc(apnStr) == true)
-    {
-        m_sApnStr = apnStr;
-        return (true);
-    }
-    else
-        return (false);
-}
-
-bool WncController::resolveUrl(uint16_t numSock, const char * url)
-{
-    bool cmdRes;
-    
-    if (numSock < MAX_NUM_WNC_SOCKETS) {
-        if (strlen(url) > 0) {
-            cmdRes = at_dnsresolve_wnc(url, &m_sSock[numSock].myIpAddressStr);
-            if (cmdRes == false)
-                dbgPuts("Cannot resolve URL!");
-            return (cmdRes);
-        }
-        else
-            dbgPuts("Invalid URL");
-    }
-    else
-        dbgPuts("Invalid Sock num!");
-
-    return (false);
-}
-
-bool WncController::setIpAddr(uint16_t numSock, const char * ipStr)
-{
-    if (numSock < MAX_NUM_WNC_SOCKETS) {
-        m_sSock[numSock].myIpAddressStr = ipStr;
-        return (true);
-    }
-    else {
-        dbgPuts("Bad socket num!");
-        return (false);
-    }
-}
-
-void WncController::setWncCmdTimeout(uint16_t toMs)
-{
-    m_sCmdTimeoutMs = toMs;
-}
-
-bool WncController::openSocketUrl(uint16_t numSock, const char * url, uint16_t port, bool tcp, uint16_t timeOutSec)
-{
-    if (resolveUrl(numSock, url) == true)
-        return (openSocket(numSock, port, tcp, timeOutSec));
-    
-    return (false);
-}
-
-bool WncController::openSocketIpAddr(uint16_t numSock, const char * ipAddr, uint16_t port, bool tcp, uint16_t timeOutSec)
-{
-    if (setIpAddr(numSock, ipAddr) == true)
-        return (openSocket(numSock, port, tcp, timeOutSec));
-    
-    return (false);
-}
- 
-bool WncController::openSocket(uint16_t numSock, uint16_t port, bool tcp, uint16_t timeOutSec)
-{
-    if (numSock < MAX_NUM_WNC_SOCKETS) {
-        // IPV4 ip addr sanity check!
-        size_t lenIpStr = m_sSock[numSock].myIpAddressStr.size();
-        if (lenIpStr < 7 || lenIpStr > 15) {
-            dbgPuts("Invalid IP Address!");
-            return (false);
-        }
-        
-        // Already open ? Must close if want to re-open with new settings.
-        if (m_sSock[numSock].open == true) {
-            dbgPuts("Socket already open, close then re-open!");
-            if (true == at_sockclose_wnc(m_sSock[numSock].numWncSock))
-                m_sSock[numSock].open = false;
-            else
-                return (false);
-        }
-        
-        m_sSock[numSock].myPort = port;
-        m_sSock[numSock].isTcp = tcp;
-        m_sSock[numSock].timeOutSec = timeOutSec;
-        
-        int16_t numWncSock = at_sockopen_wnc(m_sSock[numSock].myIpAddressStr.c_str(), port, numSock, tcp, timeOutSec);
-        m_sSock[numSock].numWncSock = numWncSock;
-        if (numWncSock > 0 && numWncSock <= MAX_NUM_WNC_SOCKETS)
-            m_sSock[numSock].open = true;
-        else {
-            m_sSock[numSock].open = false;
-            dbgPuts("Socket open fail!!!!");
-            
-            // If the modem is not responding don't bother it.
-            if (WNC_NO_RESPONSE != getWncStatus()) {
-                // Work-around.  If the sock open fails it needs to be told
-                // to close.  If 6 sock opens happen with a fail, it further
-                // crashes the WNC.  Not sure why the sock won't open.
-                at_sockclose_wnc(m_sSock[numSock].numWncSock);
-            }
-        }
-    }
-    else {
-        dbgPuts("Bad socket num or IP!");
-        return (false);
-    }
-
-    return (m_sSock[numSock].open);
-}
-
-bool WncController::sockWrite(const uint8_t * const s, uint16_t n, uint16_t numSock, bool isTcp)
-{
-    bool result = true;
-    
-    AtCmdErr_e cmdRes = at_sockwrite_wnc(s, n, m_sSock[numSock].numWncSock, isTcp);
-    if (cmdRes != WNC_AT_CMD_OK) {
-        if ((cmdRes == WNC_AT_CMD_ERREXT) || (cmdRes == WNC_AT_CMD_ERRCME))
-        {
-            // This may throw away any data that hasn't been written out of the WNC
-            //  but at this point with the way the WNC currently works we have
-            //  no choice.
-            closeOpenSocket(numSock);
-        }
-        result = false;
-    }
-    
-    return (result);
-}
-
-bool WncController::write(uint16_t numSock, const uint8_t * s, uint32_t n)
-{
-    bool result;
-    
-    if (numSock < MAX_NUM_WNC_SOCKETS) {
-        if (m_sSock[numSock].open == true) {
-            if (n <= MAX_WNC_WRITE_BYTES) {
-                result = sockWrite(s, n, numSock, m_sSock[numSock].isTcp);
-            }
-            else {
-                uint16_t rem = n % MAX_WNC_WRITE_BYTES;
-                while (n >= MAX_WNC_WRITE_BYTES) {
-                    n -= MAX_WNC_WRITE_BYTES;
-                    result = sockWrite(s, MAX_WNC_WRITE_BYTES, numSock, m_sSock[numSock].isTcp);
-                    if (result == false) {
-                        n = 0;
-                        rem = 0;
-                        dbgPuts("Sock write fail!");
-                    }
-                    else
-                        s += MAX_WNC_WRITE_BYTES;
-                }
-                if (rem > 0)
-                    result = sockWrite(s, rem, numSock, m_sSock[numSock].isTcp);                
-            }
-        }
-        else {
-            dbgPuts("Socket is closed for write!");
-            result = false;
-        }
-    }
-    else {
-        dbgPuts("Bad socket num!");
-        result = false;
-    }
-
-    return (result);
-}
-
-size_t WncController::read(uint16_t numSock, const uint8_t ** readBuf)
-{
-    static string theBuf;
-    string readStr;
-    
-    theBuf.erase();  // Clean-up from last time
-
-    if (numSock < MAX_NUM_WNC_SOCKETS) {
-        if (m_sSock[numSock].open == true) {
-            uint8_t   i = m_sSock[numSock].readRetries;
-            uint16_t to = m_sSock[numSock].readRetryWaitMs;
-            bool foundData = false;
-            do {
-                AtCmdErr_e cmdRes;
-                cmdRes = at_sockread_wnc(&readStr, m_sSock[numSock].numWncSock, m_sSock[numSock].isTcp);
-                if (WNC_AT_CMD_OK == cmdRes) {
-                    // This will let this loop read until the socket data is
-                    //  empty.  If no data, then wait the retry amount of time.
-                    if (readStr.size() > 0) {
-                        theBuf += readStr;
-                        foundData = true;
-                        i = 1;
-                    }
-                    else {
-                        // Once data is found start returning it asap
-                        if (foundData == false)
-                            waitMs(to);
-                    }
-                }
-                else {
-                    theBuf += readStr; // Append what if any we got before it errored.
-                    dbgPuts("Sockread failed!");
-                    if (WNC_NO_RESPONSE == getWncStatus()) {
-                        i = 0;
-                    }
-                    else if ((cmdRes == WNC_AT_CMD_ERREXT) || (cmdRes == WNC_AT_CMD_ERRCME))
-                    {
-                        // This may throw away any data that hasn't been read out of the WNC
-                        //  but at this point with the way the WNC currently works we have
-                        //  no choice.
-                        closeOpenSocket(numSock);
-                        i = 0;
-                    }
-                    else
-                        waitMs(to);
-                }
-            } while (i-- > 0);
-        }
-        else {
-            dbgPuts("Socket is closed for read");
-        }
-    }
-    else {
-        dbgPuts("Bad socket num!");
-    }
-
-    *readBuf = (const uint8_t *)theBuf.c_str();
-
-    return (theBuf.size());
-}
-
-size_t WncController::read(uint16_t numSock, uint8_t * readBuf, uint32_t maxReadBufLen)
-{
-    uint32_t numCopied = 0;
-    
-    if (numSock < MAX_NUM_WNC_SOCKETS) {
-        if (m_sSock[numSock].open == true) {
-            uint8_t   i = m_sSock[numSock].readRetries;
-            uint16_t to = m_sSock[numSock].readRetryWaitMs;
-            bool foundData = false;
-            uint16_t numRead;
-            do {
-                AtCmdErr_e cmdRes;
-                if (maxReadBufLen < MAX_WNC_READ_BYTES)
-                    cmdRes = at_sockread_wnc(readBuf, &numRead, maxReadBufLen, m_sSock[numSock].numWncSock, m_sSock[numSock].isTcp);
-                else
-                    cmdRes = at_sockread_wnc(readBuf, &numRead, MAX_WNC_READ_BYTES, m_sSock[numSock].numWncSock, m_sSock[numSock].isTcp);
-
-                if (WNC_AT_CMD_OK == cmdRes) {
-                    // This will let this loop read until the socket data is
-                    //  empty.  If no data, then wait the retry amount of time.
-                    if (numRead > 0) {
-                        foundData = true;
-                        i = 1;
-                        if (numRead <= maxReadBufLen) {
-                            maxReadBufLen -= numRead;
-                            numCopied     += numRead;
-                            readBuf       += numRead;
-                        }
-                        else {
-                            i = 0; // No more room for data!
-                            dbgPutsNoTime("No more room for read data!");
-                        } 
-                    }
-                    else {
-                        // Once data is found start returning it asap
-                        if (foundData == false)
-                            waitMs(to);
-                    }
-                }
-                else {
-                    dbgPuts("Sockread failed!");
-                    if (WNC_NO_RESPONSE == getWncStatus()) {
-                        i = 0;
-                    }
-                    else if ((cmdRes == WNC_AT_CMD_ERREXT) || (cmdRes == WNC_AT_CMD_ERRCME))
-                    {
-                        // This may throw away any data that hasn't been read out of the WNC
-                        //  but at this point with the way the WNC currently works we have
-                        //  no choice.
-                        closeOpenSocket(numSock);
-                        i = 0;
-                    }
-                    else
-                        waitMs(to);
-                }
-            } while ((i-- > 0) && (maxReadBufLen > 0));
-        }
-        else {
-            dbgPuts("Socket is closed for read");
-        }
-    }
-    else {
-        dbgPuts("Bad socket num!");
-    }
-
-    return (numCopied);
-}
-
-void WncController::setReadRetries(uint16_t numSock, uint16_t retries)
-{
-    if (numSock < MAX_NUM_WNC_SOCKETS)
-        m_sSock[numSock].readRetries = retries;
-    else
-        dbgPuts("Bad socket num!");
-}
-
-void WncController::setReadRetryWait(uint16_t numSock, uint16_t readRetryWaitMs)
-{
-    if (numSock < MAX_NUM_WNC_SOCKETS)
-        m_sSock[numSock].readRetryWaitMs = readRetryWaitMs;
-    else
-        dbgPuts("Bad socket num!");
-}
-
-bool WncController::closeSocket(uint16_t numSock)
-{
-    if (numSock < MAX_NUM_WNC_SOCKETS) {
-
-        if (false == at_sockclose_wnc(m_sSock[numSock].numWncSock))
-            dbgPuts("Sock close may not have closed!");
-                
-        // Even with an error the socket could have closed,
-        //  can't tell for sure so just soft close it for now.
-        m_sSock[numSock].open = false;
-    }
-    else {
-        dbgPuts("Bad socket num!");
-    }
-
-    return (m_sSock[numSock].open == false);
-}
-
-size_t WncController::mdmGetline(string * buff, int timeout_ms)
-{
-    char chin = '\0';
-    char chin_last;
-    size_t len = 0;
-
-    startTimerB();
-    while ((len <= MAX_LEN_WNC_CMD_RESPONSE) && (getTimerTicksB_mS() < timeout_ms)) {
-        if (charReady()) {
-            chin_last = chin;
-            chin = getc();
-            if (isprint(chin)) {
-                *buff += chin;
-                len++;  // Bound the copy length to something reaonable just in case
-                continue;
-            }
-            else if ((('\r' == chin_last) && ('\n' == chin)) || (('\n' == chin_last) && ('\r' == chin)))  {
-                break;
-            }
-        }
-    }
-    stopTimerB();
-    
-    if (len > MAX_LEN_WNC_CMD_RESPONSE)
-        dbgPuts("Max cmd length reply exceeded!");
-
-    return (len);
-}
-
-bool WncController::softwareInitMdm(void)
-{
-  static bool reportStatus = true;
-  unsigned i;
-  
-  if (checkCellLink() == true) {
-      if (reportStatus == false) {
-          dbgPuts("Re-connected to cellular network!");
-          reportStatus = true;
-      }
-      
-      // WNC has SIM and registered on network so 
-      //  soft initialize the WNC.
-      for (i = 0; i < WNC_SOFT_INIT_RETRY_COUNT; i++)
-          if (at_init_wnc() == true)
-              break;
-                  
-      // If it did not respond try a hardware init
-      if (i == WNC_SOFT_INIT_RETRY_COUNT)
-      {
-          at_reinitialize_mdm();
-          return (at_init_wnc(true));  // Hard reset occurred so make it go through the software init();
-      }
-      else
-          return (true);
-  }
-  else
-  {
-      if (reportStatus == true) {
-           dbgPuts("Not connected to cellular network!");
-           reportStatus = false;
-      }
-      return (false);
-  }
-}
-
-WncController::AtCmdErr_e WncController::sendWncCmd(const char * const s, string ** r, int ms_timeout)
-{
-    if (checkCellLink() == false) {
-        static string noRespStr;
-
-        // Save some run-time!
-        if (m_sDebugEnabled)
-        {
-            dbgPuts("FAIL send cmd: ", false);
-            if (m_sMoreDebugEnabled && m_sDebugEnabled) {
-                dbgPutsNoTime(s);
-            }
-            else {
-                size_t n = strlen(s);
-                if (n <= WNC_TRUNC_DEBUG_LENGTH) {
-                    dbgPutsNoTime(s);
-                }
-                else {
-                    string truncStr(s,WNC_TRUNC_DEBUG_LENGTH/2);
-                    truncStr += "..";
-                    truncStr += &s[n-(WNC_TRUNC_DEBUG_LENGTH/2)];
-                    dbgPutsNoTime(truncStr.c_str());
-                }
-            }    
-        }
-        
-        noRespStr.erase();
-        *r = &noRespStr;
-
-        return (WNC_AT_CMD_NO_CELL_LINK);
-    }
-    
-    if (m_sCheckNetStatus)
-    {
-        if (m_sMoreDebugEnabled)
-            dbgPuts("[---------- Network Status -------------");
-        string * pRespStr;
-        at_send_wnc_cmd("AT@SOCKDIAL?", &pRespStr, m_sCmdTimeoutMs);
-        if (m_sMoreDebugEnabled)
-           dbgPuts("---------------------------------------]");
-    }
-    
-    // If WNC ready, send user command
-    return (at_send_wnc_cmd(s, r, ms_timeout));
-}
-
-WncController::AtCmdErr_e WncController::at_send_wnc_cmd(const char * s, string ** r, int ms_timeout)
-{
-    // Save some run-time!
-    if (m_sDebugEnabled)
-    {
-        if (m_sMoreDebugEnabled) {
-           dbgPuts("TX: ", false); dbgPutsNoTime(s);
-        }
-        else {
-            if (m_sDebugEnabled) {  // Save some run-time!
-                size_t n = strlen(s);
-                if (n <= WNC_TRUNC_DEBUG_LENGTH) {
-                    dbgPuts("TX: ", false); dbgPutsNoTime(s);
-                }
-                else {
-                    string truncStr(s,WNC_TRUNC_DEBUG_LENGTH/2);
-                    truncStr += "..";
-                    truncStr += &s[n - (WNC_TRUNC_DEBUG_LENGTH/2)];
-                    dbgPuts("TX: ", false); dbgPutsNoTime(truncStr.c_str());
-                }
-            }
-        }
-    }
-
-    AtCmdErr_e atResult = mdmSendAtCmdRsp(s, ms_timeout, &m_sWncStr);
-    *r = &m_sWncStr;   // Return a pointer to the static string
-      
-    if (atResult != WNC_AT_CMD_TIMEOUT) {
-        // If a prior command timed out but a new one works then
-        //  change the state back to ON.  We don't know here in this 
-        //  method if the Cell Link is good so assume it is. When a command
-        //  that depends on the cell link is made it will update the state.
-        if (m_sState == WNC_NO_RESPONSE)
-            m_sState = WNC_ON;
-            
-        // Save some run-time!
-        if (m_sDebugEnabled)
-        {        
-            dbgPuts("RX: ", false);
-            if (m_sMoreDebugEnabled) {
-                dbgPutsNoTime(m_sWncStr.c_str());
-            }
-            else {
-                if (m_sWncStr.size() <= WNC_TRUNC_DEBUG_LENGTH) {
-                    dbgPutsNoTime(m_sWncStr.c_str());
-                }
-                else {
-                    string truncStr = m_sWncStr.substr(0,WNC_TRUNC_DEBUG_LENGTH/2) + "..";
-                    truncStr += m_sWncStr.substr(m_sWncStr.size() - (WNC_TRUNC_DEBUG_LENGTH/2), WNC_TRUNC_DEBUG_LENGTH/2);
-                    dbgPutsNoTime(truncStr.c_str());
-                }
-            }
-        }
-    }
-    else {
-        m_sState = WNC_NO_RESPONSE;
-        dbgPuts("AT Cmd TIMEOUT!");
-        dbgPuts("RX: ", false); dbgPutsNoTime(m_sWncStr.c_str());
-    }
-    
-    return (atResult);
-}
-
-void WncController::closeOpenSocket(uint16_t numSock)
-{
-    // Try to open and close the socket
-    do {
-        dbgPuts("Try to close and re-open socket");
-        if (false == at_sockclose_wnc(m_sSock[numSock].numWncSock)) {
-            if (WNC_NO_RESPONSE == getWncStatus()) {
-                dbgPuts("No response for closeOpenSocket1");
-                return ;
-            }
-        }        
-
-        int numWncSock = at_sockopen_wnc(m_sSock[numSock].myIpAddressStr.c_str(), m_sSock[numSock].myPort, numSock, m_sSock[numSock].isTcp, m_sSock[numSock].timeOutSec);
-        m_sSock[numSock].numWncSock = numWncSock;
-        if (numWncSock > 0 && numWncSock <= MAX_NUM_WNC_SOCKETS)
-            m_sSock[numSock].open = true;
-        else {
-            m_sSock[numSock].open = false;
-            dbgPuts("Failed to re-open socket!");
-        }
-        
-        if (WNC_NO_RESPONSE == getWncStatus()) {
-            dbgPuts("No response for closeOpenSocket2");
-            return ;
-        }
-    } while (m_sSock[numSock].open == false);
-}
-
-bool WncController::getICCID(string * iccid)
-{
-    if (at_geticcid_wnc(iccid) == false) {
-        dbgPuts("getICCID error!");
-        return (false);
-    }
-    
-    return (true);
-}
-
-bool WncController::at_geticcid_wnc(string * iccid)
-{
-    string * respStr;
-    
-    iccid->erase();
-    
-    AtCmdErr_e r = at_send_wnc_cmd("AT%CCID", &respStr, m_sCmdTimeoutMs);
-
-    if (r != WNC_AT_CMD_OK || respStr->size() == 0)
-        return (false);
-
-    size_t pos = respStr->find("AT%CCID");
-    if (pos == string::npos)
-        return (false);
-    
-    size_t posOK = respStr->rfind("OK");
-    if (posOK == string::npos)
-        return (false);
-
-    pos += 7; // Advanced to the number
-    *iccid = respStr->substr(pos, posOK - pos);
-    
-    return (true);
-}
-
-bool WncController::convertICCIDtoMSISDN(const string & iccid, string * msisdn)
-{
-    msisdn->erase();
-    
-    if (iccid.size() != 20 && iccid.size() != 19) {
-        dbgPuts("Invalid ICCID length!");
-        return (false);
-    }
- 
-    *msisdn = "882350";
-    
-    if (iccid.size() == 20)
-        *msisdn += iccid.substr(10,iccid.size() - 11);
-    else
-        *msisdn += iccid.substr(10,iccid.size() - 10);
-    
-    return (true);
-}
-
-bool WncController::sendSMSText(const char * const phoneNum, const char * const text)
-{
-    if (at_sendSMStext_wnc(phoneNum, text) == true)
-        return (true);
-    else {
-        dbgPuts("sendSMSText: Failed!");
-        return (false);
-    }
-}
-
-bool WncController::readSMSLog(struct WncSmsList * log)
-{
-    string * logStr;
-    uint16_t i;
-    
-    if (at_readSMSlog_wnc(&logStr) == false) {
-        dbgPuts("readSMSLog: Failed!");
-        return (false);
-    }
-    
-    // Clean slate    
-    log->msgCount = 0;
-
-    if (logStr->size() == 0)
-        return (false);
-
-    // Pick out the stuff from the string and convert to struct
-    string s;
-    size_t pos2;
-    size_t pos = logStr->find("+CMGL:");
-        
-    for(i=0; i<MAX_WNC_SMS_MSG_SLOTS; i++) {
-        // Start with a clean slate, let parsing fill out later.
-        log->e[i].unread = false;
-        log->e[i].incoming = false;
-        log->e[i].unsent = false;
-        log->e[i].pduMode = false;
-        log->e[i].msgReceipt = false;
-
-        log->e[i].idx = logStr->at(pos + 7);
-        if (pos == string::npos)
-            return (false);
-        pos2 = logStr->find(",\"", pos);
-        if (pos2 == string::npos) {
-            // If the WNC acts wrong and receives a PDU mode
-            //  SMS there will not be any quotes in the response,
-            //  just take the whole reply and make it the message body for
-            //  now, mark it as an unread message, set the pdu flag!
-            log->e[log->msgCount].unread = true;
-            log->e[log->msgCount].pduMode = true;
-            log->msgCount++;
-
-            pos2 = logStr->find("+CMGL", pos + 5);
-            if (pos2 == string::npos) {
-                pos2 = logStr->find("OK", pos + 5);
-                if (pos2 == string::npos) {
-                    dbgPuts("Strange SMS Log Ending!");
-                    return (false);
-                }
-                i = MAX_WNC_SMS_MSG_SLOTS;
-            }
-            log->e[log->msgCount].msg = logStr->substr(0, pos2 - pos);
-            pos = pos2;  // for loop starts off expecting pos to point to next log msg
-            continue;
-        }
-        pos += 2;  // Advance to the text we want
-        pos2 = logStr->find("\",", pos);
-        if ((pos2 == string::npos) || (pos >= pos2))
-            return (false);
-                    
-        // Setup attributes
-        s = logStr->substr(pos, pos2 - pos);
-        if (s.find("REC READ") != string::npos)
-            log->e[i].incoming = true;
-        if (s.find("REC UNREAD") != string::npos) {
-            log->e[i].unread = true;
-            log->e[i].incoming = true;
-        }
-        if (s.find("STO UNSENT") != string::npos)
-            log->e[i].unsent = true;
-        if (logStr->find(",,") == string::npos)
-            log->e[i].msgReceipt = true;
-            
-        // Tele number
-        pos2 = logStr->find(",\"", pos2);
-        if (pos2 == string::npos)
-            return (false);  
-        pos2 += 2;  // Advance to next field
-        pos = logStr->find("\",", pos2);
-        if ((pos == string::npos) || (pos2 > pos))
-            return (false);
-        if (pos == pos2)
-            log->e[i].number.erase();
-        else    
-            log->e[i].number = logStr->substr(pos2, pos - pos2);
-        
-        // Date
-        pos = logStr->find(",\"", pos);
-        if (pos == string::npos)
-            return (false);
-        pos += 2; // Beginning of date field
-        pos2 = logStr->find(",", pos); // End of timestamp field
-        if ((pos2 == string::npos) || (pos > pos2))
-            return (false);
-        if (pos == pos2)
-            log->e[i].date.erase();
-        else
-            log->e[i].date = logStr->substr(pos, pos2 - pos);
-
-        // Timestamp
-        pos = logStr->find("\",", pos2); // End of timestamp
-        if (pos == string::npos)
-            return (false);
-        pos2 += 1; // Beginning of time field
-        if (pos < pos2)
-            return (false);
-        if (pos == pos2)
-            log->e[i].time.erase();
-        else
-            log->e[i].time = logStr->substr(pos2, pos - pos2);
-        
-        // Message field
-        
-        // We don't know how many messages we have so the next search
-        // could end with +CMGL or OK.
-        pos += 2;  // Advanced to message text
-        pos2 = logStr->find("+CMGL", pos);
-        if (pos2 == string::npos) {
-            pos2 = logStr->find("OK", pos);
-            if (pos2 == string::npos) {
-                dbgPuts("Strange SMS Log Ending!");
-                return (false);
-            }
-            i = MAX_WNC_SMS_MSG_SLOTS; // break
-        }
-        if (pos > pos2)
-            return (false);
-        if (pos == pos2)
-            log->e[log->msgCount].msg.erase();
-        else
-            log->e[log->msgCount].msg = logStr->substr(pos, pos2 - pos);
-
-        log->msgCount++;  // Message complete
-    }    
-    
-    return (true);
-}
-
-bool WncController::readUnreadSMSText(struct WncSmsList * w, bool deleteRead)
-{
-    struct WncController::WncSmsList tmp;
-    
-    if (readSMSLog(&tmp) == false)
-        return (false);
-    
-    w->msgCount = 0;
-    for(uint16_t i = 0; i < tmp.msgCount; i++) {
-        if (tmp.e[i].unread == true) {
-            w->e[w->msgCount] = tmp.e[i];
-            w->msgCount++;
-            if (deleteRead == true) {
-                // Clean up message that was copied out and read
-                deleteSMSTextFromMem(w->e[i].idx);
-            }
-        }
-    }
-    
-    return (w->msgCount > 0);
-}
-
-size_t WncController::getSignalQuality(const char ** log)
-{
-    size_t n;
-
-    n = at_getSignalQuality_wnc(log);
-    if (n == 0)
-        dbgPuts("readSMSText: Failed!");
-        
-    return (n);
-}
-
-size_t WncController::at_getSignalQuality_wnc(const char ** log)
-{
-    string * pRespStr;
-    static string logStr;
-    
-    logStr.erase();
-
-    if (at_send_wnc_cmd("AT%MEAS=\"0\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
-        logStr = *pRespStr;
-        logStr += "\r\n";
-    }
-    else
-        dbgPuts("AT%MEAS=0: failed!");
-        
-    if (at_send_wnc_cmd("AT%MEAS=\"1\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
-        logStr += *pRespStr;
-        logStr += "\r\n";
-    }
-    else
-        dbgPuts("AT%MEAS=1: failed!");
-
-    if (at_send_wnc_cmd("AT%MEAS=\"2\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
-        logStr += *pRespStr;
-        logStr += "\r\n";
-    }
-    else
-        dbgPuts("AT%MEAS=2: failed!");
-
-    if (at_send_wnc_cmd("AT%MEAS=\"3\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
-        logStr += *pRespStr;
-        logStr += "\r\n";
-    }
-    else
-        dbgPuts("AT%MEAS=3: failed!");
-
-    if (at_send_wnc_cmd("AT%MEAS=\"4\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
-        logStr += *pRespStr;
-        logStr += "\r\n";
-    }
-    else
-        dbgPuts("AT%MEAS=4: failed!");
-
-    if (at_send_wnc_cmd("AT%MEAS=\"5\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
-        logStr += *pRespStr;
-        logStr += "\r\n";
-    }
-    else
-        dbgPuts("AT%MEAS=5: failed!");
-        
-    if (at_send_wnc_cmd("AT%MEAS=\"8\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
-        logStr += *pRespStr;
-        logStr += "\r\n";
-    }
-    else
-        dbgPuts("AT%MEAS=8: failed!");
-        
-    if (at_send_wnc_cmd("AT%MEAS=\"98\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
-        logStr += *pRespStr;
-        logStr += "\r\n";
-    }
-    else
-        dbgPuts("AT%MEAS=98: failed!");
-
-    *log = logStr.c_str();
-    
-    return (logStr.size());
-}
-
-bool WncController::getTimeDate(struct WncDateTime * tod)
-{
-    if (at_gettimedate_wnc(tod) == true)
-        return (true);
-    else {
-        dbgPuts("Get time date failed!");
-        return (false);
-    }
-}
-
-bool WncController::at_ping_wnc(const char * ip)
-{
-    string * pRespStr;
-    string cmdStr = "AT@PINGREQ=\"";
-    cmdStr += ip;
-    cmdStr += "\"";
-    return (at_send_wnc_cmd(cmdStr.c_str(), &pRespStr, WNC_PING_CMD_TIMEOUT_MS) == WNC_AT_CMD_OK);
-}
-
-bool WncController::at_gettimedate_wnc(struct WncDateTime * tod)
-{
-    string * pRespStr;
-    char * pEnd;
-
-    if (at_send_wnc_cmd("AT+CCLK?", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
-        if (pRespStr->size() > 0) {
-            size_t pos1 = pRespStr->find("+CCLK:");
-            if (pos1 != string::npos) {
-                pEnd = (char *)pRespStr->c_str() + pos1 + 8;
-                tod->year  = strtol(pEnd, &pEnd, 10);
-                tod->month = strtol(pEnd+1, &pEnd, 10);
-                tod->day   = strtol(pEnd+1, &pEnd, 10);
-                tod->hour  = strtol(pEnd+1, &pEnd, 10);
-                tod->min   = strtol(pEnd+1, &pEnd, 10);
-                tod->sec   = strtol(pEnd+1, &pEnd, 10);
-                return (true);
-            }
-        }
-    }
-
-    return (false);
-}
-
-bool WncController::at_get_wnc_net_stats(WncIpStats * s)
-{
-    string * pRespStr;
-    AtCmdErr_e cmdRes = at_send_wnc_cmd("AT+CGCONTRDP=1", &pRespStr, m_sCmdTimeoutMs);
-    
-    if (WNC_AT_CMD_OK == cmdRes) {
-        if (pRespStr->size() > 0) {
-            memset((void*)s, '\0', sizeof(*s));  // Clean-up
-            string ss;
-            size_t pe;
-            size_t ps = pRespStr->rfind("\"");
-            if (ps != string::npos) {
-                ps += 2;  // Skip the , after the "
-                pe = ps;
-
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-
-                ss = pRespStr->substr(ps, pe - 1 - ps);
-                strncpy(s->ip, ss.c_str(), MAX_LEN_IP_STR);
-                s->ip[MAX_LEN_IP_STR - 1] = '\0';
-                ps = pe;
-
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(",", pe);
-
-                ss = pRespStr->substr(ps, pe - ps);
-                strncpy(s->mask, ss.c_str(), MAX_LEN_IP_STR);
-                s->mask[MAX_LEN_IP_STR - 1] = '\0';
-                ps = pe + 1;
-
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(",", pe);
-
-                ss = pRespStr->substr(ps, pe - ps);
-                strncpy(s->gateway, ss.c_str(), MAX_LEN_IP_STR);
-                s->gateway[MAX_LEN_IP_STR - 1] = '\0';
-                ps = pe + 1;
-
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(",", pe);
-
-
-                ss = pRespStr->substr(ps, pe - ps);
-                strncpy(s->dnsPrimary, ss.c_str(), MAX_LEN_IP_STR);
-                s->dnsPrimary[MAX_LEN_IP_STR - 1] = '\0';
-                ps = pe + 1;
-
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(".", pe);
-                if (pe == string::npos)
-                    return (false);
-                else
-                    pe += 1;
-                pe = pRespStr->find(",", pe);
-
-
-                ss = pRespStr->substr(ps, pe - ps);
-                strncpy(s->dnsSecondary, ss.c_str(), MAX_LEN_IP_STR);
-                s->dnsSecondary[MAX_LEN_IP_STR - 1] = '\0';
-                
-                dbgPuts("~~~~~~~~~~ WNC IP Stats ~~~~~~~~~~~~");
-                dbgPuts("ip: ", false);      dbgPutsNoTime(s->ip);
-                dbgPuts("mask: ", false);    dbgPutsNoTime(s->mask);
-                dbgPuts("gateway: ", false); dbgPutsNoTime(s->gateway);
-                dbgPuts("dns pri: ", false); dbgPutsNoTime(s->dnsPrimary);
-                dbgPuts("dns sec: ", false); dbgPutsNoTime(s->dnsSecondary);
-                dbgPuts("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
-            
-                return (true);
-            }
-        }
-    }
-
-    return (false);
-}
-
-bool WncController::deleteSMSTextFromMem(char msgIdx)
-{
-    const char * err = "deleteSMSTextFromMem: Failed!";
-    
-    switch (msgIdx)
-    {
-        case '*':
-            at_deleteSMSTextFromMem_wnc('1');
-            at_deleteSMSTextFromMem_wnc('2');
-            at_deleteSMSTextFromMem_wnc('3');
-            return (true); // WNC may error if slot empty, just ignore!
-
-        case '1':
-        case '2':
-        case '3':
-            if (true == at_deleteSMSTextFromMem_wnc(msgIdx))
-                return (true);
-            else {
-                dbgPuts(err);
-                return (false);
-            }
-
-        default:
-            dbgPuts(err);
-            return (false);
-    }
-}
-
-bool WncController::sendSMSTextFromMem(char msgIdx)
-{
-    const char * err = "deleteSMSTextFromMem: Failed!";
-    
-    switch (msgIdx)
-    {
-        case '*':
-            at_sendSMStextMem_wnc('1');
-            at_sendSMStextMem_wnc('2');
-            at_sendSMStextMem_wnc('3');
-            return (true); // WNC may error if slot is empty, just ignore!
-
-        case '1':
-        case '2':
-        case '3':
-            if (at_sendSMStextMem_wnc(msgIdx) == true)
-                return (true);
-            else {
-                dbgPuts(err);
-                return (false);
-            }
-
-        default:
-            dbgPuts(err);
-            return (false);
-    }
-}
-
-bool WncController::at_deleteSMSTextFromMem_wnc(char n)
-{
-    string cmdStr, respStr;
-    // Message is stored in WNC, now send it!
-    cmdStr = "AT+CMGD=";
-    cmdStr += n;
-    cmdStr += "\r\n";
-    dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str(), false);
-    AtCmdErr_e r = mdmSendAtCmdRsp(cmdStr.c_str(), m_sCmdTimeoutMs, &respStr);
-    dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
-    return (r == WNC_AT_CMD_OK);
-}
-
-bool WncController::at_sendSMStextMem_wnc(char n)
-{
-    string cmdStr, respStr;
-    // Message is stored in WNC, now send it!
-    cmdStr = "AT+CMSS=";
-    cmdStr += n;
-    cmdStr += "\r\n";
-    dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str(), false);
-    AtCmdErr_e r = mdmSendAtCmdRsp(cmdStr.c_str(), m_sCmdTimeoutMs, &respStr);
-    dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
-    return (r == WNC_AT_CMD_OK);
-}    
-
-bool WncController::at_sendSMStext_wnc(const char * const phoneNum, const char * const text)
-{
-    string respStr;
-    string * pRespStr;
-    size_t l = strlen(text);
-    
-    if (l <= MAX_WNC_SMS_LENGTH)
-    {
-        // Check to see if the SMS service is available
-        checkCellLink();
-        if (m_sReadyForSMS == true) {
-            at_send_wnc_cmd("AT+CMGF=1", &pRespStr, m_sCmdTimeoutMs);
-            string cmdStr("AT+CMGS=\"");
-            cmdStr += phoneNum;
-            cmdStr += "\"";
-            dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str());
-            cmdStr += "\x0d"; // x0d = <ENTER>
-            // Send raw command with short timeout (the timeout will fail cause the WNC is not supposed to reply yet!
-            // And we want a delay before sending the actual text part of the string!
-            mdmSendAtCmdRsp(cmdStr.c_str(), 300, &respStr, false);  //  False turns off auto-addition of CR+LF (the WNC wants nothing here)
-            dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
-            if ((respStr.size() > 0) && (respStr.find("ERROR") == string::npos)) {
-                // Part 2 of the text, this is the actual text part:
-                cmdStr = text;
-                dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str());
-                cmdStr += "\x1A";  // <CTRL>-Z is what tells the WNC the message is complete to send!
-                AtCmdErr_e r = mdmSendAtCmdRsp(cmdStr.c_str(), 10000, &respStr);
-                dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
-                if (respStr.size() == 0)
-                    return (false);
-                else
-                    return (r == WNC_AT_CMD_OK);
-            }
-        }
-    }
-
-    return (false);
-}
-
-bool WncController::saveSMSText(const char * const phoneNum, const char * const text, char * msgIdx)
-{
-    if (at_saveSMStext_wnc(phoneNum, text, msgIdx) == true)
-        return (true);
-    else {
-        dbgPuts("saveSMSTextToMem: failed!\r\n");
-        return (false);
-    }
-}
-
-bool WncController::at_saveSMStext_wnc(const char * const phoneNum, const char * const text, char * msgIdx)
-{
-    string respStr;
-    size_t l = strlen(text);
-    
-    if (l <= MAX_WNC_SMS_LENGTH)
-    {
-        // Check to see if the SMS service is available
-        checkCellLink();
-        if (m_sReadyForSMS == true) {
-            string cmdStr("AT+CMGW=\"");
-            cmdStr += phoneNum;
-            cmdStr += "\"";
-            dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str());
-            cmdStr += "\x0d"; // x0d = <ENTER>
-            // Send raw command with short timeout (the timeout will fail cause the WNC is not supposed to reply yet!
-            // And we want a delay before sending the actual text part of the string!
-            mdmSendAtCmdRsp(cmdStr.c_str(), 300, &respStr, false);  //  False turns off auto-addition of CR+LF (the WNC wants nothing here)
-            dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
-            if ((respStr.size() > 0) && (respStr.find("ERROR") == string::npos)) {
-                // Part 2 of the text, this is the actual text part:
-                cmdStr = text;
-                dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str());
-                cmdStr += "\x1A";  // <CTRL>-Z is what tells the WNC the message is complete to save!
-                AtCmdErr_e r = mdmSendAtCmdRsp(cmdStr.c_str(), 10000, &respStr);
-                dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
-                if (respStr.size() > 0) {
-                    // respStr will have the SMS index
-                    size_t pos1 = respStr.find("+CMGW: ");
-                    size_t pos2 = respStr.rfind("OK");
-                    if (pos1 != string::npos && pos2 != string::npos) {
-                        *msgIdx = *string(respStr.substr(pos1+7, 1)).c_str();
-                        return (true);
-                    }
-                    else {
-                        *msgIdx = '!';
-                    }
-                }
-            }
-        }
-    }
-        
-    return (false);
-}
-
-bool WncController::at_readSMSlog_wnc(string ** log)
-{
-    return (at_send_wnc_cmd("AT+CMGL", log, m_sCmdTimeoutMs) == WNC_AT_CMD_OK);
-}
-
-size_t WncController::at_readSMStext_wnc(const char n, const char ** log)
-{
-    static string smsReadTxtStr;
-    string * pRespStr;
-    string cmdStr;
-        
-    smsReadTxtStr.erase();
-    cmdStr = "AT+CMGR";
-    cmdStr += '1';
-    if (at_send_wnc_cmd("AT+CMGR", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK)
-        *log = pRespStr->c_str();
-    else
-        *log = "\0";
-        
-    return (pRespStr->size());
-}
-
-bool WncController::at_at_wnc(void)
-{
-    string * pRespStr;
-    return (WNC_AT_CMD_OK == at_send_wnc_cmd("AT", &pRespStr, WNC_QUICK_CMD_TIMEOUT_MS)); // Heartbeat?
-}
-
-bool WncController::at_init_wnc(bool hardReset)
-{
-  string * pRespStr;
-  AtCmdErr_e cmdRes;
-  
-  if (hardReset == true)
-      dbgPuts("Hard Soft Reset!");
-  
-  dbgPuts("Start AT init of WNC:");
-  
-  // Kick it twice to perhaps remove cued responses from an incomplete
-  //  power cycle.
-  at_send_wnc_cmd("AT", &pRespStr, WNC_QUICK_CMD_TIMEOUT_MS);
-  at_send_wnc_cmd("AT", &pRespStr, WNC_QUICK_CMD_TIMEOUT_MS);
-  
-  // Dump the firmware revision on the debug log:
-  at_send_wnc_cmd("AT+GMR", &pRespStr, m_sCmdTimeoutMs);
-
-  // Quick commands below do not need to check cellular connectivity
-  at_send_wnc_cmd("ATE0", &pRespStr, WNC_QUICK_CMD_TIMEOUT_MS);  // Echo Off
-  at_send_wnc_cmd("AT+CMEE=2", &pRespStr, m_sCmdTimeoutMs);      // 2 - verbose error, 1 - numeric error, 0 - just ERROR
-
-  // Setup 3 memory slots in the WNC SIM for SMS usage.
-  at_send_wnc_cmd("AT+CMGF=1", &pRespStr, m_sCmdTimeoutMs);
-  at_send_wnc_cmd("AT+CPMS=\"SM\",\"SM\",\"SM\"", &pRespStr, m_sCmdTimeoutMs);
-
-  cmdRes = at_send_wnc_cmd("AT", &pRespStr, WNC_QUICK_CMD_TIMEOUT_MS);     // Heartbeat?
-  
-  // If the simple commands are not working, no chance of more complex.
-  //  I have seen re-trying commands make it worse.
-  if (cmdRes != WNC_AT_CMD_OK)
-      return (false);
-  
-  cmdRes = at_send_wnc_cmd("AT@INTERNET=1", &pRespStr, m_sCmdTimeoutMs);
-  if (cmdRes != WNC_AT_CMD_OK)
-      return (false);
-  
-  cmdRes = at_send_wnc_cmd("AT@SOCKDIAL=1", &pRespStr, m_sCmdTimeoutMs);
-  if (cmdRes != WNC_AT_CMD_OK)
-      return (false);
-  
-  dbgPuts("SUCCESS: AT init of WNC!");
-  
-  return (true);
-}
-
-int16_t WncController::at_sockopen_wnc(const char * const ip, uint16_t port, uint16_t numSock, bool tcp, uint16_t timeOutSec)
-{
-    string * pRespStr;
-    string cmd_str("AT@SOCKCREAT=");
-    AtCmdErr_e res;
-
-    if (tcp) cmd_str += "1";  // TCP
-    else cmd_str += "2";      // else UDP
-
-    cmd_str += ",0";
-    res = sendWncCmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
-    if (res == WNC_AT_CMD_OK && pRespStr->size() > 0)
-    {
-        size_t pos1 = pRespStr->find("T:");
-        size_t pos2 = pRespStr->rfind("OK");
-        if ((pos1 != string::npos) && (pos2 != string::npos)) {
-            size_t numLen = pos2 - (pos1 + 2);
-            string sockStr = pRespStr->substr(pos1 + 2, numLen);
-            cmd_str = "AT@SOCKCONN=";
-            cmd_str += sockStr;
-            cmd_str += ",\"";
-            cmd_str += ip;
-            cmd_str += "\",";
-            cmd_str += _to_string(port);
-            cmd_str += ",";
-            if (timeOutSec < 30)
-                timeOutSec = 30;
-            else if (timeOutSec > 360)
-                timeOutSec = 360;
-            cmd_str += _to_string(timeOutSec);
-            res = sendWncCmd(cmd_str.c_str(), &pRespStr, 1000 * timeOutSec + 1000);
-            if (m_sMoreDebugEnabled) {
-                at_send_wnc_cmd("AT@SOCKCREAT?", &pRespStr, m_sCmdTimeoutMs);
-                at_send_wnc_cmd("AT@SOCKCONN?", &pRespStr, m_sCmdTimeoutMs);
-            }
-            return (strtol(sockStr.c_str(), NULL, 10));
-        }
-        else {
-            dbgPuts("Invalid sockcreat response!");
-            return (0);
-        }
-    }
-    else
-        return (0);
-}
-
-bool WncController::at_sockclose_wnc(uint16_t numSock)
-{
-    string * pRespStr;
-    string cmd_str("AT@SOCKCLOSE=");
-
-    cmd_str += _to_string(numSock);
- 
-    // Don't check the cell status to close the socket
-    AtCmdErr_e res = at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
-    
-    if ((res != WNC_AT_CMD_TIMEOUT) && (res != WNC_AT_CMD_OK)) {
-        for (unsigned i = 0; i < WNC_SOCK_CLOSE_RETRY_CNT; i++) {
-            res = at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
-            if ((res == WNC_AT_CMD_TIMEOUT) || (res == WNC_AT_CMD_OK))
-                break;
-        }
-    }
-    
-    return (res == WNC_AT_CMD_OK); 
-}
-
-bool WncController::at_dnsresolve_wnc(const char * s, string * ipStr)
-{
-    string * pRespStr;
-    string str(s);
-    AtCmdErr_e r;
-
-    ipStr->erase(); // Clear out string until resolved!
-    str = "AT@DNSRESVDON=\"" + str;
-    str += "\"";
-    r = sendWncCmd(str.c_str(), &pRespStr, WNC_DNS_RESOLVE_WAIT_MS);
-    if (r == WNC_AT_CMD_OK && pRespStr->size() > 0) {
-        size_t pos_start = pRespStr->find(":\"") + 2;
-        if (pos_start !=  string::npos) {
-            size_t pos_end = pRespStr->find("\"", pos_start) - 1;
-            if (pos_end != string::npos) {
-                if (pos_end > pos_start) {
-                    // Make a copy for use later (the source string is re-used)
-                    *ipStr = pRespStr->substr(pos_start, pos_end - pos_start + 1);
-                    return (true);
-                }
-            }
-        }
-    }
-
-    *ipStr = INVALID_IP_STR;
-
-    return (false);
-}
-
-bool WncController::waitForPowerOnModemToRespond(uint8_t timeoutSecs)
-{
-    // Now, give the modem x seconds to start responding by
-    // sending simple 'AT' commands to modem once per second.
-    if (timeoutSecs > 0) {
-        do {
-            timeoutSecs--;
-            dbgPutsNoTime("\rWaiting ", false); dbgPutsNoTime(_to_string(timeoutSecs), false);
-            dbgPutsNoTime(" ", false);
-            AtCmdErr_e rc = mdmSendAtCmdRsp("AT", 500, &m_sWncStr);
-            if (rc == WNC_AT_CMD_OK) {
-                dbgPutsNoTime("");  // CR LF
-                return true; //timer.read();
-            }
-            waitMs(500);
-        }
-        while (timeoutSecs > 0);    
-        dbgPutsNoTime(""); // CR LF
-    }
-    
-    return (false);
-}
-
-WncController::AtCmdErr_e WncController::at_sockwrite_wnc(const uint8_t * s, uint16_t n, uint16_t numSock, bool isTcp)
-{
-    AtCmdErr_e result;
-
-    if ((n > 0) && (n <= MAX_WNC_WRITE_BYTES)) {
-        string * pRespStr;
-        const char * num2str;
-        string cmd_str;
-
-        if (isTcp == true)
-            cmd_str="AT@SOCKWRITE=";
-        else
-            cmd_str="AT@SOCKWRITE="; // "AT@SOCKSEND=";
-
-        cmd_str += _to_string(numSock);
-        cmd_str += ",";
-        cmd_str += _to_string(n);
-        cmd_str += ",\"";
-        while(n > 0) {
-            n--;
-            num2str = _to_hex_string(*s++);
-            // Always 2-digit ascii hex:
-            if (num2str[1] == '\0')
-                cmd_str += '0';
-            cmd_str += num2str;
-        }
-        cmd_str += "\"";
-        result = sendWncCmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
-    }
-    else {
-        dbgPuts("sockwrite Err, string len bad!");
-        result = WNC_AT_CMD_ERR;
-    }
-    
-    return (result);
-}
-
-WncController::AtCmdErr_e WncController::at_sockread_wnc(string * pS, uint16_t numSock, bool isTcp)
-{
-    AtCmdErr_e result = WNC_AT_CMD_OK;
-
-    string * pRespStr;
-    string cmd_str;
-    size_t pos_start, pos_end;
-    int i;
-    
-    pS->erase();  // Start with a fresh string
-
-    if (isTcp == true)
-        cmd_str="AT@SOCKREAD=";
-    else
-        cmd_str="AT@SOCKREAD="; // "AT@SOCKRECV=";
-
-    cmd_str += _to_string(numSock);
-    cmd_str += ",";
-    cmd_str += _to_string(MAX_WNC_READ_BYTES);
-            
-    // Experimental: read should not need to check cell net status
-    result = at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
-    if (result == WNC_AT_CMD_OK) {
-        if (pRespStr->size() > 0) {
-            pos_start = pRespStr->find("\"");
-            pos_end   = pRespStr->rfind("\"");
-            // Make sure search finds what it's looking for!
-            if (pos_start != string::npos && pos_end != string::npos) {
-                pos_start++;
-                i = pos_end - pos_start;  // Num hex chars, 2 per byte
-            }
-            else
-                i = 0;
-        }
-        else
-            i = 0;
-            
-        if ((i < 0) || ((i % 2) == 1))
-            dbgPuts("Invalid READ string!");
-        
-        if (i > 2*MAX_WNC_READ_BYTES) {
-            i = 2*MAX_WNC_READ_BYTES;
-            dbgPuts("DANGER WNC read data does not match length!");
-        }
-            
-        // If data, convert the hex string into byte values
-        while (i > 0) {
-            i -= 2;
-            *pS += (uint8_t)strtol(pRespStr->substr(pos_start, 2).c_str(), NULL, 16);
-            pos_start += 2;
-        }
-    }
-
-    return (result);
-}
-
-WncController::AtCmdErr_e WncController::at_sockread_wnc(uint8_t * pS, uint16_t * numRead, uint16_t n, uint16_t numSock, bool isTcp)
-{
-    AtCmdErr_e result = WNC_AT_CMD_OK;
-    *numRead = 0;
-    
-    if ((n > 0) && (n <= MAX_WNC_READ_BYTES)) {
-        string * pRespStr;
-        string cmd_str;
-        size_t pos_start, pos_end;
-        int i;
-
-        if (isTcp == true)
-            cmd_str="AT@SOCKREAD=";
-        else
-            cmd_str="AT@SOCKREAD="; // "AT@SOCKRECV=";
-
-        cmd_str += _to_string(numSock);
-        cmd_str += ",";
-        cmd_str += _to_string(n);
-            
-        // Experimental: read should not need to check cell net status
-        result = at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
-        if (result == WNC_AT_CMD_OK) {
-            if (pRespStr->size() > 0) {
-                pos_start = pRespStr->find("\"");
-                pos_end   = pRespStr->rfind("\"");
-                // Make sure search finds what it's looking for!
-                if (pos_start != string::npos && pos_end != string::npos) {
-                    pos_start++;
-                    i = pos_end - pos_start;  // Num hex chars, 2 per byte
-                }
-                else
-                    i = 0;
-            }
-            else
-                i = 0;
-                
-            if ((i < 0) || ((i % 2) == 1))
-                dbgPuts("Invalid READ string!");
-                
-            if (i > 2*n) {
-                // Bound the ill formated WNC read string!
-                i = 2*n;
-                dbgPuts("TRUNCATING read data!");
-            }
-
-            // If data, convert the hex string into byte values
-            i /= 2;
-            *numRead = i;
-            while (i > 0) {
-                i--;
-                *pS++ = (uint8_t)strtol(pRespStr->substr(pos_start, 2).c_str(), NULL, 16);
-                pos_start += 2;
-            }
-        }
-    }
-    else {
-        dbgPuts("sockread Err, to many to read!");
-        result = WNC_AT_CMD_ERR;
-    }
-
-    return (result);
-}
-
-bool WncController::at_reinitialize_mdm(void)
-{
-     // Atempt to re-register
-//     string * pRespStr;
-//     dbgPuts("Force re-register!");
-//     at_send_wnc_cmd("AT+CFUN=0,0", &pRespStr, m_sCmdTimeoutMs);
-//     waitMs(31000);
-//     at_send_wnc_cmd("AT+CFUN=1,0", &pRespStr, m_sCmdTimeoutMs);
-//     waitMs(31000);
-    
-    // Initialize the modem
-    dbgPuts("Modem RE-initializing with SOFT Reset...");
-
-    string * pRespStr;
-    at_send_wnc_cmd("AT@DMREBOOT", &pRespStr, m_sCmdTimeoutMs);
-    waitMs(5000);
-
-    // Now, give the modem time to start responding by
-    // sending simple 'AT' commands to the modem once per second.
-    int timeoutSecs = WNC_REINIT_MAX_TIME_MS;
-    do {
-        dbgPuts("\rWaiting ", false); dbgPutsNoTime(_to_string(timeoutSecs), false);
-        AtCmdErr_e rc = mdmSendAtCmdRsp("AT", 500, &m_sWncStr);
-        if (rc == WNC_AT_CMD_OK) {
-            dbgPutsNoTime("");  // CR LF
-            break;
-        }
-        waitMs(500);
-        timeoutSecs--;
-    }
-    while (timeoutSecs > 0);    
-    
-    if (timeoutSecs <= 0)
-        dbgPuts("\r\nModem RE-init FAILED!");
-    else
-        dbgPuts("\r\nModem RE-init complete!");
-        
-    return (timeoutSecs > 0);
-}
-
-WncController::AtCmdErr_e WncController::mdmSendAtCmdRsp(const char *cmd, int timeout_ms, string * rsp, bool crLf)
-{
-    rsp->erase(); // Clean up from possible prior cmd response
-
-    // Don't bother the WNC if user hasn't turned it on.
-    if (m_sState == WNC_OFF)
-        return (WNC_AT_CMD_WNC_NOT_ON);
-        
-    size_t n = strlen(cmd);
-    
-    // Wait per WNC advise
-    waitMs(WNC_WAIT_FOR_AT_CMD_MS);
- 
-    if (cmd && n > 0) {
-        sendCmd(cmd, crLf);
-//        sendCmd(cmd, n, 1000, crLf);  // 3rd arg is micro seconds between chars sent
-    }
-
-    startTimerA();
-    while (getTimerTicksA_mS() < timeout_ms) {
-        n = mdmGetline(rsp, timeout_ms - getTimerTicksA_mS());
-
-        if (n == 0)
-            continue;
-        
-        if (rsp->rfind("OK") != string::npos) {
-            stopTimerA();
-            return (WNC_AT_CMD_OK);
-        }
-        
-        if (rsp->rfind("+CME ERROR") != string::npos) {
-            stopTimerA();
-            return (WNC_AT_CMD_ERRCME);
-        }
-        
-        if (rsp->rfind("@EXTERR") != string::npos) {
-            stopTimerA();
-            return (WNC_AT_CMD_ERREXT);
-        }
-            
-        if (rsp->rfind("ERROR") != string::npos) {
-            stopTimerA();
-            return (WNC_AT_CMD_ERR);
-        }
-    }
-    stopTimerA();
-    
-    return (WNC_AT_CMD_TIMEOUT);
-}
-
-bool WncController::at_setapn_wnc(const char * const apnStr)
-{
-    string * pRespStr;
-    
-    string cmd_str("AT%PDNSET=1,");
-    cmd_str += apnStr;
-    cmd_str += ",IP";
-    if (WNC_AT_CMD_OK == at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, WNC_APNSET_TIMEOUT_MS))  // Set APN, cmd seems to take a little longer sometimes
-        return (true);
-    else
-        return (false);
-}
-
-bool WncController::at_getrssiber_wnc(int16_t * dBm, int16_t * ber)
-{
-    string * pRespStr;
-    AtCmdErr_e cmdRes;    
-    cmdRes = at_send_wnc_cmd("AT+CSQ", &pRespStr, m_sCmdTimeoutMs);       // Check RSSI,BER
-    if (cmdRes != WNC_AT_CMD_OK)
-        return (false);
-    
-    if (pRespStr->size() == 0) {
-        dbgPuts("Strange RSSI result!");
-        return (false);
-    }
-    else {
-        size_t pos1 = pRespStr->find("SQ:");
-        size_t pos2 = pRespStr->rfind(",");
-        // Sanity check
-        if ((pos1 != string::npos) && (pos2 != string::npos) && (pos2 > pos1)) {
-            string subStr = pRespStr->substr(pos1 + 4, pos2 - pos1 );
-            int rawRssi = atoi(subStr.c_str());
-        
-            // Convert WNC RSSI into dBm range:
-            //  0 - -113 dBm
-            //  1 - -111 dBm
-            //  2..30 - -109 to -53 dBm
-            //  31 - -51dBm or >
-            //  99 - not known or not detectable
-            if (rawRssi == 99)
-                *dBm = -199;
-            else if (rawRssi == 0)
-                *dBm = -113;
-            else if (rawRssi == 1)
-                *dBm = -111;
-            else if (rawRssi == 31)
-                *dBm = -51;
-            else if (rawRssi >= 2 && rawRssi <= 30)
-                *dBm = -113 + 2 * rawRssi;
-            else {
-                dbgPuts("Invalid RSSI!");
-                return (false);
-            }
-            // Parse out BER: 0..7 as RXQUAL values in the table 3GPP TS 45.008 subclause 8.2.4
-            //                99 - unknown or undetectable
-            subStr = pRespStr->substr(pos2 + 1, pRespStr->length() - (pos2 + 1));
-            *ber = atoi(subStr.c_str());
-        }
-        else {
-            dbgPuts("Strange RSSI result2!");
-            return (false);
-        }
-    }
-    
-    return (true);
-}
-
-bool WncController::checkCellLink(void)
-{
-    string * pRespStr;
-    size_t pos;
-    int regSts;
-    int cmdRes1, cmdRes2;
-
-    if (m_sState == WNC_OFF)
-        return (false);
-    
-    m_sState = WNC_ON_NO_CELL_LINK;
-
-    if (m_sMoreDebugEnabled)
-        dbgPuts("<-------- Begin Cell Status ------------");
-
-    cmdRes1 = at_send_wnc_cmd("AT+CSQ", &pRespStr, m_sCmdTimeoutMs);       // Check RSSI,BER
-
-    // If no response, don't bother with more commands
-    if (cmdRes1 != WNC_AT_CMD_TIMEOUT)
-        cmdRes2 = at_send_wnc_cmd("AT+CPIN?", &pRespStr, m_sCmdTimeoutMs);     // Check if SIM locked
-    else {
-        if (m_sMoreDebugEnabled)
-            dbgPuts("------------ WNC No Response! --------->");
-
-        return (false);
-    }
-    
-    if ((cmdRes1 != WNC_AT_CMD_OK) || (cmdRes2 != WNC_AT_CMD_OK) || (pRespStr->size() == 0))
-    {
-        if (m_sMoreDebugEnabled)
-        {
-            if ((cmdRes1 == WNC_AT_CMD_TIMEOUT) || (cmdRes2 == WNC_AT_CMD_TIMEOUT))
-                dbgPuts("------------ WNC No Response! --------->");
-            else
-                dbgPuts("------------ WNC Cmd Error! ----------->");
-        }
-        
-        // If by a miracle it responds to the 2nd after the 1st, keep going
-        if ((cmdRes2 == WNC_AT_CMD_TIMEOUT) || (pRespStr->size() == 0))
-            return (false);      
-    }
-  
-    // If SIM Card not ready don't bother with commands!
-    if (pRespStr->find("CPIN: READY") == string::npos)
-    {
-        if (m_sMoreDebugEnabled)
-            dbgPuts("------------ WNC SIM Problem! --------->");
-
-        return (false);
-    }
-
-    // SIM card OK, now check for signal and cellular network registration
-    cmdRes1 = at_send_wnc_cmd("AT+CREG?", &pRespStr, m_sCmdTimeoutMs);      // Check if registered on network
-    if (cmdRes1 != WNC_AT_CMD_OK || pRespStr->size() == 0)
-    {
-        if (m_sMoreDebugEnabled)
-            dbgPuts("------------ WNC +CREG? Fail! --------->");
-
-        return (false);
-    }
-    else
-    {
-        pos = pRespStr->find("CREG: ");
-        if (pos != string::npos)
-        {
-            // The registration is the 2nd arg in the comma separated list
-            *pRespStr = pRespStr->substr(pos+8, 1);
-            regSts = atoi(pRespStr->c_str());
-            switch (regSts) {
-                case 1:
-                case 5:
-                case 6:
-                case 7:
-                    m_sReadyForSMS = true;
-                    break;
-                default:
-                    m_sReadyForSMS = false;
-                    dbgPuts("SMS Service Down!");
-            }
-
-            // 1 - registered home, 5 - registered roaming
-            if ((regSts != 1) && (regSts != 5))
-            {
-                if (m_sMoreDebugEnabled)
-                    dbgPuts("------ WNC Cell Link Down for Data! --->");
-
-                return (false);
-            }
-        }
-
-        if (m_sMoreDebugEnabled)
-            dbgPuts("------------ WNC Ready ---------------->");
-    }
-    
-    // If we made it this far and the WNC did respond, keep the ON state
-    if (m_sState != WNC_NO_RESPONSE)
-        m_sState = WNC_ON;
-    
-    return (true);
-}
-
-int WncController::dbgPutsNoTime(const char * s, bool crlf)
-{
-    if (m_sDebugEnabled == true) {
-        int r = dbgWriteChars(s);
-        if (crlf == true)
-            return (dbgWriteChars("\r\n"));
-        else
-            return (r);
-    }
-    else
-        return 0;
-};
-
-int WncController::dbgPuts(const char * s, bool crlf)
-{
-    dbgPutsNoTime("[*] ", false);
-    dbgPutsNoTime(_to_string(getLogTimerTicks()), false);
-    dbgPutsNoTime(" ", false);
-
-    int r = dbgPutsNoTime(s, false);
-    
-    if (crlf == true)
-        return (dbgPutsNoTime("", true));
-    else
-        return (r);
-};
-    
-void WncController::sendCmd(const char * cmd, bool crLf)
-{
-    puts(cmd);
-    if (crLf == true)
-        puts("\r\n");
-}
-
-void WncController::sendCmd(const char * cmd, unsigned n, unsigned wait_uS, bool crLf)
-{
-    while (n--) {
-        putc(*cmd++);
-        waitUs(wait_uS);
-    };
-    if (crLf == true) {
-        putc('\r');
-        waitUs(wait_uS);
-        putc('\n');
-        waitUs(wait_uS);
-    }
-}
-
-}; // End namespace WncController_fk
+/*
+    Copyright (c) 2016 Fred Kellerman
+ 
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+ 
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+ 
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+    THE SOFTWARE.
+    
+    @file          WncController.cpp
+    @purpose       Controls WNC 14A2A Cellular Modem
+    @version       1.0
+    @date          July 2016
+    @author        Fred Kellerman
+    
+    
+    An Example of usage:
+    
+    WncControllerK64F mdm(&wncPinList, &mdmUart, &debugUart);
+
+    mdm.enableDebug(true, true);
+
+    if (false == mdm.powerWncOn("m2m.com.attz", 60)) {
+        while(1);
+    }
+
+    // ICCID and MSISDN
+    string iccid; string msisdn;
+    if (mdm.getICCID(&iccid) == true) {
+        if (mdm.convertICCIDtoMSISDN(iccid, &msisdn) == true) {
+           // Send an SMS message (must use 15-digit MISDN number!)
+           mdm.sendSMSText(msisdn.c_str(), "Hello from WNC Kit -> from WNC");
+        }
+    }
+
+    // Get an IP address setup for the socket #1 (0 indexed))
+    if (true == mdm.resolveUrl(0, "www.att.com"))
+    {
+        // Report server IP
+        if (true == mdm.getIpAddr(0, ipAddrStr)) {
+            debugUart.puts("Server IP: ");
+            debugUart.puts(ipAddrStr);
+            debugUart.puts("\r\n");
+        }
+
+        // Open Socket #1, TCP=true resolved IP on port 80:
+        if (true == mdm.openSocket(0, 80, true)) {
+            // Write some data
+            const uint8_t * dataStr = "GET /index.html HTTP/1.0\r\nFrom: someuser@someuser.com\r\nUser-Agent: HTTPTool/1.0\r\n\r\n";
+            if (true == mdm.write(0, dataStr, strlen((const char *)dataStr)))
+            {
+                const uint8_t * myBuf;
+                mdm.setReadRetries(0, 20);
+                uint32_t n = mdm.read(0, &myBuf);
+                if (n > 0)
+                    debugUart.printf("Read %d chars: %s\r\n", n, myBuf);
+                else
+                    debugUart.puts("No read data!\r\n");
+            }
+        }
+    }
+    
+*/
+
+
+#include <cstdlib>
+#include <cctype>
+#include <string.h>
+#include "WncController.h"
+
+namespace WncController_fk {
+
+/////////////////////////////////////////////////////
+// Static initializers
+/////////////////////////////////////////////////////
+WncController::WncSocketInfo_s WncController::m_sSock[MAX_NUM_WNC_SOCKETS];
+const WncController::WncSocketInfo_s WncController::defaultSockStruct = { 0, false, "192.168.0.1", 80, 0, 25, true, 30 };
+
+WncController::WncState_e WncController::m_sState = WNC_OFF;
+uint16_t WncController::m_sCmdTimeoutMs = WNC_CMD_TIMEOUT_MS;
+string WncController::m_sApnStr = "NULL";
+string WncController::m_sWncStr;
+uint8_t WncController::m_sPowerUpTimeoutSecs = MAX_POWERUP_TIMEOUT;
+bool WncController::m_sDebugEnabled = false;
+bool WncController::m_sMoreDebugEnabled = false;
+bool WncController::m_sCheckNetStatus = false;   // Turn on internet status check between every command
+const char * const WncController::INVALID_IP_STR = "";
+bool WncController::m_sReadyForSMS = false;
+
+
+/**
+ * C++ version 0.4 char* style "itoa":
+ * Written by Lukás Chmela
+ * Released under GPLv3.
+ */
+static char* itoa(int64_t value, char* result, int base)
+{
+    // check that the base is valid
+    if ( base < 2 || base > 36 ) {
+        *result = '\0';
+        return result;
+    }
+
+    char* ptr = result, *ptr1 = result, tmp_char;
+    int64_t tmp_value;
+
+    do {
+        tmp_value = value;
+        value /= base;
+        *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)];
+    } while ( value );
+
+    // Apply negative sign
+    if ( tmp_value < 0 )
+        *ptr++ = '-';
+
+    *ptr-- = '\0';
+
+    while ( ptr1 < ptr ) {
+        tmp_char = *ptr;
+        *ptr-- = *ptr1;
+        *ptr1++ = tmp_char;
+    }
+
+    return result;
+}
+
+const char * WncController::_to_string(int64_t value)
+{
+    static char str[21];  // room for signed 64-bit + null
+    itoa(value, str, 10);
+    return (str);
+}
+
+const char * WncController::_to_hex_string(uint8_t value)
+{
+    static char str[3];   // room for 8-bit + null
+    itoa(value, str, 16);
+    return (str);
+}
+
+WncController::WncController(void)
+{
+    for(unsigned i; i<MAX_NUM_WNC_SOCKETS; i++)
+        m_sSock[i] = defaultSockStruct;
+}
+
+void WncController::enableDebug(bool on, bool moreDebugOn)
+{
+    m_sDebugEnabled = on;
+    m_sMoreDebugEnabled = moreDebugOn;
+}
+
+WncController::WncState_e WncController::getWncStatus(void)
+{
+    return (m_sState);
+}
+
+int16_t WncController::getDbmRssi(void)
+{
+    int16_t rssi, ber;
+    if (at_getrssiber_wnc(&rssi, &ber) == true)
+        return (rssi);
+    else
+        return (99);
+}
+
+int16_t WncController::get3gBer(void)
+{
+    int16_t rssi, ber;
+    if (at_getrssiber_wnc(&rssi, &ber) == true)
+        return (ber);
+    else
+        return (99);
+}
+
+bool WncController::powerWncOn(const char * const apn, uint8_t powerUpTimeoutSecs)
+{
+    dbgPuts("Waiting for WNC to Initialize...");
+    m_sPowerUpTimeoutSecs = powerUpTimeoutSecs;
+    m_sState = WNC_ON_NO_CELL_LINK;  // Turn soft on to allow "AT" for init to be sent!
+    if (initWncModem(powerUpTimeoutSecs) == true) {
+        // Set the Apn
+        setApnName(apn);
+        if (false == softwareInitMdm()) {
+            dbgPuts("Software init failed!");
+            m_sState = WNC_OFF;
+        }
+    }
+    else {
+        dbgPuts("Power up failed!");
+        m_sState = WNC_OFF;
+    }
+    
+    return ((m_sState == WNC_ON) || (m_sState == WNC_ON_NO_CELL_LINK));
+}
+
+size_t WncController::sendCustomCmd(const char * cmd, char * resp, size_t sizeRespBuf, int ms_timeout)
+{
+    string * respStr;
+    
+    if (sizeRespBuf > 0) {
+        AtCmdErr_e r = at_send_wnc_cmd(cmd, &respStr, ms_timeout);
+        strncpy(resp, respStr->c_str(), sizeRespBuf);
+        if (respStr->size() > sizeRespBuf)
+            dbgPuts("sendCustomCmd truncated!");
+            
+        return (respStr->size());
+    }
+    
+    dbgPuts("sendCustomCmd: would have overrun!");
+    
+    return (0);
+}
+
+bool WncController::pingUrl(const char * url)
+{
+    string ipAddr;
+    
+    if (true == at_dnsresolve_wnc(url, &ipAddr))
+        return (pingIp(ipAddr.c_str()));
+    else
+        dbgPuts("pingUrl DNS resolve: failed!");
+        
+    return (false);
+}
+
+bool WncController::pingIp(const char * ip)
+{
+    if (true == at_ping_wnc(ip))
+        return (true);
+    else
+        dbgPuts("pingIp: failed!");
+    
+    return (false);
+}
+
+bool WncController::getWncNetworkingStats(WncIpStats * s)
+{
+    return (at_get_wnc_net_stats(s));
+}
+
+bool WncController::getIpAddr(uint16_t numSock, char myIpAddr[MAX_LEN_IP_STR])
+{
+    if (numSock < MAX_NUM_WNC_SOCKETS) {
+        strncpy(myIpAddr, m_sSock[numSock].myIpAddressStr.c_str(), MAX_LEN_IP_STR);
+        myIpAddr[MAX_LEN_IP_STR - 1] = '\0';
+        return (true);
+    }
+    else {
+        myIpAddr[0] = '\0';
+        return (false);
+    }
+}
+
+bool WncController::setApnName(const char * const apnStr)
+{
+    if (at_setapn_wnc(apnStr) == true)
+    {
+        m_sApnStr = apnStr;
+        return (true);
+    }
+    else
+        return (false);
+}
+
+bool WncController::resolveUrl(uint16_t numSock, const char * url)
+{
+    bool cmdRes;
+    
+    if (numSock < MAX_NUM_WNC_SOCKETS) {
+        if (strlen(url) > 0) {
+            cmdRes = at_dnsresolve_wnc(url, &m_sSock[numSock].myIpAddressStr);
+            if (cmdRes == false)
+                dbgPuts("Cannot resolve URL!");
+            return (cmdRes);
+        }
+        else
+            dbgPuts("Invalid URL");
+    }
+    else
+        dbgPuts("Invalid Sock num!");
+
+    return (false);
+}
+
+bool WncController::setIpAddr(uint16_t numSock, const char * ipStr)
+{
+    if (numSock < MAX_NUM_WNC_SOCKETS) {
+        m_sSock[numSock].myIpAddressStr = ipStr;
+        return (true);
+    }
+    else {
+        dbgPuts("Bad socket num!");
+        return (false);
+    }
+}
+
+void WncController::setWncCmdTimeout(uint16_t toMs)
+{
+    m_sCmdTimeoutMs = toMs;
+}
+
+bool WncController::openSocketUrl(uint16_t numSock, const char * url, uint16_t port, bool tcp, uint16_t timeOutSec)
+{
+    if (resolveUrl(numSock, url) == true)
+        return (openSocket(numSock, port, tcp, timeOutSec));
+    
+    return (false);
+}
+
+bool WncController::openSocketIpAddr(uint16_t numSock, const char * ipAddr, uint16_t port, bool tcp, uint16_t timeOutSec)
+{
+    if (setIpAddr(numSock, ipAddr) == true)
+        return (openSocket(numSock, port, tcp, timeOutSec));
+    
+    return (false);
+}
+ 
+bool WncController::openSocket(uint16_t numSock, uint16_t port, bool tcp, uint16_t timeOutSec)
+{
+    if (numSock < MAX_NUM_WNC_SOCKETS) {
+        // IPV4 ip addr sanity check!
+        size_t lenIpStr = m_sSock[numSock].myIpAddressStr.size();
+        if (lenIpStr < 7 || lenIpStr > 15) {
+            dbgPuts("Invalid IP Address!");
+            return (false);
+        }
+        
+        // Already open ? Must close if want to re-open with new settings.
+        if (m_sSock[numSock].open == true) {
+            dbgPuts("Socket already open, close then re-open!");
+            if (true == at_sockclose_wnc(m_sSock[numSock].numWncSock))
+                m_sSock[numSock].open = false;
+            else
+                return (false);
+        }
+        
+        m_sSock[numSock].myPort = port;
+        m_sSock[numSock].isTcp = tcp;
+        m_sSock[numSock].timeOutSec = timeOutSec;
+        
+        int16_t numWncSock = at_sockopen_wnc(m_sSock[numSock].myIpAddressStr.c_str(), port, numSock, tcp, timeOutSec);
+        m_sSock[numSock].numWncSock = numWncSock;
+        if (numWncSock > 0 && numWncSock <= MAX_NUM_WNC_SOCKETS)
+            m_sSock[numSock].open = true;
+        else {
+            m_sSock[numSock].open = false;
+            dbgPuts("Socket open fail!!!!");
+            
+            // If the modem is not responding don't bother it.
+            if (WNC_NO_RESPONSE != getWncStatus()) {
+                // Work-around.  If the sock open fails it needs to be told
+                // to close.  If 6 sock opens happen with a fail, it further
+                // crashes the WNC.  Not sure why the sock won't open.
+                at_sockclose_wnc(m_sSock[numSock].numWncSock);
+            }
+        }
+    }
+    else {
+        dbgPuts("Bad socket num or IP!");
+        return (false);
+    }
+
+    return (m_sSock[numSock].open);
+}
+
+bool WncController::sockWrite(const uint8_t * const s, uint16_t n, uint16_t numSock, bool isTcp)
+{
+    bool result = true;
+    
+    AtCmdErr_e cmdRes = at_sockwrite_wnc(s, n, m_sSock[numSock].numWncSock, isTcp);
+    if (cmdRes != WNC_AT_CMD_OK) {
+        if ((cmdRes == WNC_AT_CMD_ERREXT) || (cmdRes == WNC_AT_CMD_ERRCME))
+        {
+            // This may throw away any data that hasn't been written out of the WNC
+            //  but at this point with the way the WNC currently works we have
+            //  no choice.
+            closeOpenSocket(numSock);
+        }
+        result = false;
+    }
+    
+    return (result);
+}
+
+bool WncController::write(uint16_t numSock, const uint8_t * s, uint32_t n)
+{
+    bool result;
+    
+    if (numSock < MAX_NUM_WNC_SOCKETS) {
+        if (m_sSock[numSock].open == true) {
+            if (n <= MAX_WNC_WRITE_BYTES) {
+                result = sockWrite(s, n, numSock, m_sSock[numSock].isTcp);
+            }
+            else {
+                uint16_t rem = n % MAX_WNC_WRITE_BYTES;
+                while (n >= MAX_WNC_WRITE_BYTES) {
+                    n -= MAX_WNC_WRITE_BYTES;
+                    result = sockWrite(s, MAX_WNC_WRITE_BYTES, numSock, m_sSock[numSock].isTcp);
+                    if (result == false) {
+                        n = 0;
+                        rem = 0;
+                        dbgPuts("Sock write fail!");
+                    }
+                    else
+                        s += MAX_WNC_WRITE_BYTES;
+                }
+                if (rem > 0)
+                    result = sockWrite(s, rem, numSock, m_sSock[numSock].isTcp);                
+            }
+        }
+        else {
+            dbgPuts("Socket is closed for write!");
+            result = false;
+        }
+    }
+    else {
+        dbgPuts("Bad socket num!");
+        result = false;
+    }
+
+    return (result);
+}
+
+size_t WncController::read(uint16_t numSock, const uint8_t ** readBuf)
+{
+    static string theBuf;
+    string readStr;
+    
+    theBuf.erase();  // Clean-up from last time
+
+    if (numSock < MAX_NUM_WNC_SOCKETS) {
+        if (m_sSock[numSock].open == true) {
+            uint8_t   i = m_sSock[numSock].readRetries;
+            uint16_t to = m_sSock[numSock].readRetryWaitMs;
+            bool foundData = false;
+            do {
+                AtCmdErr_e cmdRes;
+                cmdRes = at_sockread_wnc(&readStr, m_sSock[numSock].numWncSock, m_sSock[numSock].isTcp);
+                if (WNC_AT_CMD_OK == cmdRes) {
+                    // This will let this loop read until the socket data is
+                    //  empty.  If no data, then wait the retry amount of time.
+                    if (readStr.size() > 0) {
+                        theBuf += readStr;
+                        foundData = true;
+                        i = 1;
+                    }
+                    else {
+                        // Once data is found start returning it asap
+                        if (foundData == false)
+                            waitMs(to);
+                    }
+                }
+                else {
+                    theBuf += readStr; // Append what if any we got before it errored.
+                    dbgPuts("Sockread failed!");
+                    if (WNC_NO_RESPONSE == getWncStatus()) {
+                        i = 0;
+                    }
+                    else if ((cmdRes == WNC_AT_CMD_ERREXT) || (cmdRes == WNC_AT_CMD_ERRCME))
+                    {
+                        // This may throw away any data that hasn't been read out of the WNC
+                        //  but at this point with the way the WNC currently works we have
+                        //  no choice.
+                        closeOpenSocket(numSock);
+                        i = 0;
+                    }
+                    else
+                        waitMs(to);
+                }
+            } while (i-- > 0);
+        }
+        else {
+            dbgPuts("Socket is closed for read");
+        }
+    }
+    else {
+        dbgPuts("Bad socket num!");
+    }
+
+    *readBuf = (const uint8_t *)theBuf.c_str();
+
+    return (theBuf.size());
+}
+
+size_t WncController::read(uint16_t numSock, uint8_t * readBuf, uint32_t maxReadBufLen)
+{
+    uint32_t numCopied = 0;
+    
+    if (numSock < MAX_NUM_WNC_SOCKETS) {
+        if (m_sSock[numSock].open == true) {
+            uint8_t   i = m_sSock[numSock].readRetries;
+            uint16_t to = m_sSock[numSock].readRetryWaitMs;
+            bool foundData = false;
+            uint16_t numRead;
+            do {
+                AtCmdErr_e cmdRes;
+                if (maxReadBufLen < MAX_WNC_READ_BYTES)
+                    cmdRes = at_sockread_wnc(readBuf, &numRead, maxReadBufLen, m_sSock[numSock].numWncSock, m_sSock[numSock].isTcp);
+                else
+                    cmdRes = at_sockread_wnc(readBuf, &numRead, MAX_WNC_READ_BYTES, m_sSock[numSock].numWncSock, m_sSock[numSock].isTcp);
+
+                if (WNC_AT_CMD_OK == cmdRes) {
+                    // This will let this loop read until the socket data is
+                    //  empty.  If no data, then wait the retry amount of time.
+                    if (numRead > 0) {
+                        foundData = true;
+                        i = 1;
+                        if (numRead <= maxReadBufLen) {
+                            maxReadBufLen -= numRead;
+                            numCopied     += numRead;
+                            readBuf       += numRead;
+                        }
+                        else {
+                            i = 0; // No more room for data!
+                            dbgPutsNoTime("No more room for read data!");
+                        } 
+                    }
+                    else {
+                        // Once data is found start returning it asap
+                        if (foundData == false)
+                            waitMs(to);
+                    }
+                }
+                else {
+                    dbgPuts("Sockread failed!");
+                    if (WNC_NO_RESPONSE == getWncStatus()) {
+                        i = 0;
+                    }
+                    else if ((cmdRes == WNC_AT_CMD_ERREXT) || (cmdRes == WNC_AT_CMD_ERRCME))
+                    {
+                        // This may throw away any data that hasn't been read out of the WNC
+                        //  but at this point with the way the WNC currently works we have
+                        //  no choice.
+                        closeOpenSocket(numSock);
+                        i = 0;
+                    }
+                    else
+                        waitMs(to);
+                }
+            } while ((i-- > 0) && (maxReadBufLen > 0));
+        }
+        else {
+            dbgPuts("Socket is closed for read");
+        }
+    }
+    else {
+        dbgPuts("Bad socket num!");
+    }
+
+    return (numCopied);
+}
+
+void WncController::setReadRetries(uint16_t numSock, uint16_t retries)
+{
+    if (numSock < MAX_NUM_WNC_SOCKETS)
+        m_sSock[numSock].readRetries = retries;
+    else
+        dbgPuts("Bad socket num!");
+}
+
+void WncController::setReadRetryWait(uint16_t numSock, uint16_t readRetryWaitMs)
+{
+    if (numSock < MAX_NUM_WNC_SOCKETS)
+        m_sSock[numSock].readRetryWaitMs = readRetryWaitMs;
+    else
+        dbgPuts("Bad socket num!");
+}
+
+bool WncController::closeSocket(uint16_t numSock)
+{
+    if (numSock < MAX_NUM_WNC_SOCKETS) {
+
+        if (false == at_sockclose_wnc(m_sSock[numSock].numWncSock))
+            dbgPuts("Sock close may not have closed!");
+                
+        // Even with an error the socket could have closed,
+        //  can't tell for sure so just soft close it for now.
+        m_sSock[numSock].open = false;
+    }
+    else {
+        dbgPuts("Bad socket num!");
+    }
+
+    return (m_sSock[numSock].open == false);
+}
+
+size_t WncController::mdmGetline(string * buff, int timeout_ms)
+{
+    char chin = '\0';
+    char chin_last;
+    size_t len = 0;
+
+    startTimerB();
+    while ((len <= MAX_LEN_WNC_CMD_RESPONSE) && (getTimerTicksB_mS() < timeout_ms)) {
+        if (charReady()) {
+            chin_last = chin;
+            chin = getc();
+            if (isprint(chin)) {
+                *buff += chin;
+                len++;  // Bound the copy length to something reaonable just in case
+                continue;
+            }
+            else if ((('\r' == chin_last) && ('\n' == chin)) || (('\n' == chin_last) && ('\r' == chin)))  {
+                break;
+            }
+        }
+    }
+    stopTimerB();
+    
+    if (len > MAX_LEN_WNC_CMD_RESPONSE)
+        dbgPuts("Max cmd length reply exceeded!");
+
+    return (len);
+}
+
+bool WncController::softwareInitMdm(void)
+{
+  static bool reportStatus = true;
+  unsigned i;
+  
+  if (checkCellLink() == true) {
+      if (reportStatus == false) {
+          dbgPuts("Re-connected to cellular network!");
+          reportStatus = true;
+      }
+      
+      // WNC has SIM and registered on network so 
+      //  soft initialize the WNC.
+      for (i = 0; i < WNC_SOFT_INIT_RETRY_COUNT; i++)
+          if (at_init_wnc() == true)
+              break;
+                  
+      // If it did not respond try a hardware init
+      if (i == WNC_SOFT_INIT_RETRY_COUNT)
+      {
+          at_reinitialize_mdm();
+          return (at_init_wnc(true));  // Hard reset occurred so make it go through the software init();
+      }
+      else
+          return (true);
+  }
+  else
+  {
+      if (reportStatus == true) {
+           dbgPuts("Not connected to cellular network!");
+           reportStatus = false;
+      }
+      return (false);
+  }
+}
+
+WncController::AtCmdErr_e WncController::sendWncCmd(const char * const s, string ** r, int ms_timeout)
+{
+    if (checkCellLink() == false) {
+        static string noRespStr;
+
+        // Save some run-time!
+        if (m_sDebugEnabled)
+        {
+            dbgPuts("FAIL send cmd: ", false);
+            if (m_sMoreDebugEnabled && m_sDebugEnabled) {
+                dbgPutsNoTime(s);
+            }
+            else {
+                size_t n = strlen(s);
+                if (n <= WNC_TRUNC_DEBUG_LENGTH) {
+                    dbgPutsNoTime(s);
+                }
+                else {
+                    string truncStr(s,WNC_TRUNC_DEBUG_LENGTH/2);
+                    truncStr += "..";
+                    truncStr += &s[n-(WNC_TRUNC_DEBUG_LENGTH/2)];
+                    dbgPutsNoTime(truncStr.c_str());
+                }
+            }    
+        }
+        
+        noRespStr.erase();
+        *r = &noRespStr;
+
+        return (WNC_AT_CMD_NO_CELL_LINK);
+    }
+    
+    if (m_sCheckNetStatus)
+    {
+        if (m_sMoreDebugEnabled)
+            dbgPuts("[---------- Network Status -------------");
+        string * pRespStr;
+        at_send_wnc_cmd("AT@SOCKDIAL?", &pRespStr, m_sCmdTimeoutMs);
+        if (m_sMoreDebugEnabled)
+           dbgPuts("---------------------------------------]");
+    }
+    
+    // If WNC ready, send user command
+    return (at_send_wnc_cmd(s, r, ms_timeout));
+}
+
+WncController::AtCmdErr_e WncController::at_send_wnc_cmd(const char * s, string ** r, int ms_timeout)
+{
+    // Save some run-time!
+    if (m_sDebugEnabled)
+    {
+        if (m_sMoreDebugEnabled) {
+           dbgPuts("TX: ", false); dbgPutsNoTime(s);
+        }
+        else {
+            if (m_sDebugEnabled) {  // Save some run-time!
+                size_t n = strlen(s);
+                if (n <= WNC_TRUNC_DEBUG_LENGTH) {
+                    dbgPuts("TX: ", false); dbgPutsNoTime(s);
+                }
+                else {
+                    string truncStr(s,WNC_TRUNC_DEBUG_LENGTH/2);
+                    truncStr += "..";
+                    truncStr += &s[n - (WNC_TRUNC_DEBUG_LENGTH/2)];
+                    dbgPuts("TX: ", false); dbgPutsNoTime(truncStr.c_str());
+                }
+            }
+        }
+    }
+
+    AtCmdErr_e atResult = mdmSendAtCmdRsp(s, ms_timeout, &m_sWncStr);
+    *r = &m_sWncStr;   // Return a pointer to the static string
+      
+    if (atResult != WNC_AT_CMD_TIMEOUT) {
+        // If a prior command timed out but a new one works then
+        //  change the state back to ON.  We don't know here in this 
+        //  method if the Cell Link is good so assume it is. When a command
+        //  that depends on the cell link is made it will update the state.
+        if (m_sState == WNC_NO_RESPONSE)
+            m_sState = WNC_ON;
+            
+        // Save some run-time!
+        if (m_sDebugEnabled)
+        {        
+            dbgPuts("RX: ", false);
+            if (m_sMoreDebugEnabled) {
+                dbgPutsNoTime(m_sWncStr.c_str());
+            }
+            else {
+                if (m_sWncStr.size() <= WNC_TRUNC_DEBUG_LENGTH) {
+                    dbgPutsNoTime(m_sWncStr.c_str());
+                }
+                else {
+                    string truncStr = m_sWncStr.substr(0,WNC_TRUNC_DEBUG_LENGTH/2) + "..";
+                    truncStr += m_sWncStr.substr(m_sWncStr.size() - (WNC_TRUNC_DEBUG_LENGTH/2), WNC_TRUNC_DEBUG_LENGTH/2);
+                    dbgPutsNoTime(truncStr.c_str());
+                }
+            }
+        }
+    }
+    else {
+        m_sState = WNC_NO_RESPONSE;
+        dbgPuts("AT Cmd TIMEOUT!");
+        dbgPuts("RX: ", false); dbgPutsNoTime(m_sWncStr.c_str());
+    }
+    
+    return (atResult);
+}
+
+void WncController::closeOpenSocket(uint16_t numSock)
+{
+    // Try to open and close the socket
+    do {
+        dbgPuts("Try to close and re-open socket");
+        if (false == at_sockclose_wnc(m_sSock[numSock].numWncSock)) {
+            if (WNC_NO_RESPONSE == getWncStatus()) {
+                dbgPuts("No response for closeOpenSocket1");
+                return ;
+            }
+        }        
+
+        int numWncSock = at_sockopen_wnc(m_sSock[numSock].myIpAddressStr.c_str(), m_sSock[numSock].myPort, numSock, m_sSock[numSock].isTcp, m_sSock[numSock].timeOutSec);
+        m_sSock[numSock].numWncSock = numWncSock;
+        if (numWncSock > 0 && numWncSock <= MAX_NUM_WNC_SOCKETS)
+            m_sSock[numSock].open = true;
+        else {
+            m_sSock[numSock].open = false;
+            dbgPuts("Failed to re-open socket!");
+        }
+        
+        if (WNC_NO_RESPONSE == getWncStatus()) {
+            dbgPuts("No response for closeOpenSocket2");
+            return ;
+        }
+    } while (m_sSock[numSock].open == false);
+}
+
+bool WncController::getICCID(string * iccid)
+{
+    if (at_geticcid_wnc(iccid) == false) {
+        dbgPuts("getICCID error!");
+        return (false);
+    }
+    
+    return (true);
+}
+
+bool WncController::at_geticcid_wnc(string * iccid)
+{
+    string * respStr;
+    
+    iccid->erase();
+    
+    AtCmdErr_e r = at_send_wnc_cmd("AT%CCID", &respStr, m_sCmdTimeoutMs);
+
+    if (r != WNC_AT_CMD_OK || respStr->size() == 0)
+        return (false);
+
+    size_t pos = respStr->find("AT%CCID");
+    if (pos == string::npos)
+        return (false);
+    
+    size_t posOK = respStr->rfind("OK");
+    if (posOK == string::npos)
+        return (false);
+
+    pos += 7; // Advanced to the number
+    *iccid = respStr->substr(pos, posOK - pos);
+    
+    return (true);
+}
+
+bool WncController::convertICCIDtoMSISDN(const string & iccid, string * msisdn)
+{
+    msisdn->erase();
+    
+    if (iccid.size() != 20 && iccid.size() != 19) {
+        dbgPuts("Invalid ICCID length!");
+        return (false);
+    }
+ 
+    *msisdn = "882350";
+    
+    if (iccid.size() == 20)
+        *msisdn += iccid.substr(10,iccid.size() - 11);
+    else
+        *msisdn += iccid.substr(10,iccid.size() - 10);
+    
+    return (true);
+}
+
+bool WncController::sendSMSText(const char * const phoneNum, const char * const text)
+{
+    if (at_sendSMStext_wnc(phoneNum, text) == true)
+        return (true);
+    else {
+        dbgPuts("sendSMSText: Failed!");
+        return (false);
+    }
+}
+
+bool WncController::readSMSLog(struct WncSmsList * log)
+{
+    string * logStr;
+    uint16_t i;
+    
+    if (at_readSMSlog_wnc(&logStr) == false) {
+        dbgPuts("readSMSLog: Failed!");
+        return (false);
+    }
+    
+    // Clean slate    
+    log->msgCount = 0;
+
+    if (logStr->size() == 0)
+        return (false);
+
+    // Pick out the stuff from the string and convert to struct
+    string s;
+    size_t pos2;
+    size_t pos = logStr->find("+CMGL:");
+        
+    for(i=0; i<MAX_WNC_SMS_MSG_SLOTS; i++) {
+        // Start with a clean slate, let parsing fill out later.
+        log->e[i].unread = false;
+        log->e[i].incoming = false;
+        log->e[i].unsent = false;
+        log->e[i].pduMode = false;
+        log->e[i].msgReceipt = false;
+
+        log->e[i].idx = logStr->at(pos + 7);
+        if (pos == string::npos)
+            return (false);
+        pos2 = logStr->find(",\"", pos);
+        if (pos2 == string::npos) {
+            // If the WNC acts wrong and receives a PDU mode
+            //  SMS there will not be any quotes in the response,
+            //  just take the whole reply and make it the message body for
+            //  now, mark it as an unread message, set the pdu flag!
+            log->e[log->msgCount].unread = true;
+            log->e[log->msgCount].pduMode = true;
+            log->msgCount++;
+
+            pos2 = logStr->find("+CMGL", pos + 5);
+            if (pos2 == string::npos) {
+                pos2 = logStr->find("OK", pos + 5);
+                if (pos2 == string::npos) {
+                    dbgPuts("Strange SMS Log Ending!");
+                    return (false);
+                }
+                i = MAX_WNC_SMS_MSG_SLOTS;
+            }
+            log->e[log->msgCount].msg = logStr->substr(0, pos2 - pos);
+            pos = pos2;  // for loop starts off expecting pos to point to next log msg
+            continue;
+        }
+        pos += 2;  // Advance to the text we want
+        pos2 = logStr->find("\",", pos);
+        if ((pos2 == string::npos) || (pos >= pos2))
+            return (false);
+                    
+        // Setup attributes
+        s = logStr->substr(pos, pos2 - pos);
+        if (s.find("REC READ") != string::npos)
+            log->e[i].incoming = true;
+        if (s.find("REC UNREAD") != string::npos) {
+            log->e[i].unread = true;
+            log->e[i].incoming = true;
+        }
+        if (s.find("STO UNSENT") != string::npos)
+            log->e[i].unsent = true;
+        if (logStr->find(",,") == string::npos)
+            log->e[i].msgReceipt = true;
+            
+        // Tele number
+        pos2 = logStr->find(",\"", pos2);
+        if (pos2 == string::npos)
+            return (false);  
+        pos2 += 2;  // Advance to next field
+        pos = logStr->find("\",", pos2);
+        if ((pos == string::npos) || (pos2 > pos))
+            return (false);
+        if (pos == pos2)
+            log->e[i].number.erase();
+        else    
+            log->e[i].number = logStr->substr(pos2, pos - pos2);
+        
+        // Date
+        pos = logStr->find(",\"", pos);
+        if (pos == string::npos)
+            return (false);
+        pos += 2; // Beginning of date field
+        pos2 = logStr->find(",", pos); // End of timestamp field
+        if ((pos2 == string::npos) || (pos > pos2))
+            return (false);
+        if (pos == pos2)
+            log->e[i].date.erase();
+        else
+            log->e[i].date = logStr->substr(pos, pos2 - pos);
+
+        // Timestamp
+        pos = logStr->find("\",", pos2); // End of timestamp
+        if (pos == string::npos)
+            return (false);
+        pos2 += 1; // Beginning of time field
+        if (pos < pos2)
+            return (false);
+        if (pos == pos2)
+            log->e[i].time.erase();
+        else
+            log->e[i].time = logStr->substr(pos2, pos - pos2);
+        
+        // Message field
+        
+        // We don't know how many messages we have so the next search
+        // could end with +CMGL or OK.
+        pos += 2;  // Advanced to message text
+        pos2 = logStr->find("+CMGL", pos);
+        if (pos2 == string::npos) {
+            pos2 = logStr->find("OK", pos);
+            if (pos2 == string::npos) {
+                dbgPuts("Strange SMS Log Ending!");
+                return (false);
+            }
+            i = MAX_WNC_SMS_MSG_SLOTS; // break
+        }
+        if (pos > pos2)
+            return (false);
+        if (pos == pos2)
+            log->e[log->msgCount].msg.erase();
+        else
+            log->e[log->msgCount].msg = logStr->substr(pos, pos2 - pos);
+
+        log->msgCount++;  // Message complete
+    }    
+    
+    return (true);
+}
+
+bool WncController::readUnreadSMSText(struct WncSmsList * w, bool deleteRead)
+{
+    struct WncController::WncSmsList tmp;
+    
+    if (readSMSLog(&tmp) == false)
+        return (false);
+    
+    w->msgCount = 0;
+    for(uint16_t i = 0; i < tmp.msgCount; i++) {
+        if (tmp.e[i].unread == true) {
+            w->e[w->msgCount] = tmp.e[i];
+            w->msgCount++;
+            if (deleteRead == true) {
+                // Clean up message that was copied out and read
+                deleteSMSTextFromMem(w->e[i].idx);
+            }
+        }
+    }
+    
+    return (w->msgCount > 0);
+}
+
+size_t WncController::getSignalQuality(const char ** log)
+{
+    size_t n;
+
+    n = at_getSignalQuality_wnc(log);
+    if (n == 0)
+        dbgPuts("readSMSText: Failed!");
+        
+    return (n);
+}
+
+size_t WncController::at_getSignalQuality_wnc(const char ** log)
+{
+    string * pRespStr;
+    static string logStr;
+    
+    logStr.erase();
+
+    if (at_send_wnc_cmd("AT%MEAS=\"0\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
+        logStr = *pRespStr;
+        logStr += "\r\n";
+    }
+    else
+        dbgPuts("AT%MEAS=0: failed!");
+        
+    if (at_send_wnc_cmd("AT%MEAS=\"1\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
+        logStr += *pRespStr;
+        logStr += "\r\n";
+    }
+    else
+        dbgPuts("AT%MEAS=1: failed!");
+
+    if (at_send_wnc_cmd("AT%MEAS=\"2\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
+        logStr += *pRespStr;
+        logStr += "\r\n";
+    }
+    else
+        dbgPuts("AT%MEAS=2: failed!");
+
+    if (at_send_wnc_cmd("AT%MEAS=\"3\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
+        logStr += *pRespStr;
+        logStr += "\r\n";
+    }
+    else
+        dbgPuts("AT%MEAS=3: failed!");
+
+    if (at_send_wnc_cmd("AT%MEAS=\"4\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
+        logStr += *pRespStr;
+        logStr += "\r\n";
+    }
+    else
+        dbgPuts("AT%MEAS=4: failed!");
+
+    if (at_send_wnc_cmd("AT%MEAS=\"5\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
+        logStr += *pRespStr;
+        logStr += "\r\n";
+    }
+    else
+        dbgPuts("AT%MEAS=5: failed!");
+        
+    if (at_send_wnc_cmd("AT%MEAS=\"8\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
+        logStr += *pRespStr;
+        logStr += "\r\n";
+    }
+    else
+        dbgPuts("AT%MEAS=8: failed!");
+        
+    if (at_send_wnc_cmd("AT%MEAS=\"98\"", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
+        logStr += *pRespStr;
+        logStr += "\r\n";
+    }
+    else
+        dbgPuts("AT%MEAS=98: failed!");
+
+    *log = logStr.c_str();
+    
+    return (logStr.size());
+}
+
+bool WncController::getTimeDate(struct WncDateTime * tod)
+{
+    if (at_gettimedate_wnc(tod) == true)
+        return (true);
+    else {
+        dbgPuts("Get time date failed!");
+        return (false);
+    }
+}
+
+bool WncController::at_ping_wnc(const char * ip)
+{
+    string * pRespStr;
+    string cmdStr = "AT@PINGREQ=\"";
+    cmdStr += ip;
+    cmdStr += "\"";
+    return (at_send_wnc_cmd(cmdStr.c_str(), &pRespStr, WNC_PING_CMD_TIMEOUT_MS) == WNC_AT_CMD_OK);
+}
+
+bool WncController::at_gettimedate_wnc(struct WncDateTime * tod)
+{
+    string * pRespStr;
+    char * pEnd;
+
+    if (at_send_wnc_cmd("AT+CCLK?", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK) {
+        if (pRespStr->size() > 0) {
+            size_t pos1 = pRespStr->find("+CCLK:");
+            if (pos1 != string::npos) {
+                pEnd = (char *)pRespStr->c_str() + pos1 + 8;
+                tod->year  = strtol(pEnd, &pEnd, 10);
+                tod->month = strtol(pEnd+1, &pEnd, 10);
+                tod->day   = strtol(pEnd+1, &pEnd, 10);
+                tod->hour  = strtol(pEnd+1, &pEnd, 10);
+                tod->min   = strtol(pEnd+1, &pEnd, 10);
+                tod->sec   = strtol(pEnd+1, &pEnd, 10);
+                return (true);
+            }
+        }
+    }
+
+    return (false);
+}
+
+bool WncController::at_get_wnc_net_stats(WncIpStats * s)
+{
+    string * pRespStr;
+    AtCmdErr_e cmdRes = at_send_wnc_cmd("AT+CGCONTRDP=1", &pRespStr, m_sCmdTimeoutMs);
+    
+    if (WNC_AT_CMD_OK == cmdRes) {
+        if (pRespStr->size() > 0) {
+            memset((void*)s, '\0', sizeof(*s));  // Clean-up
+            string ss;
+            size_t pe;
+            size_t ps = pRespStr->rfind("\"");
+            if (ps != string::npos) {
+                ps += 2;  // Skip the , after the "
+                pe = ps;
+
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+
+                ss = pRespStr->substr(ps, pe - 1 - ps);
+                strncpy(s->ip, ss.c_str(), MAX_LEN_IP_STR);
+                s->ip[MAX_LEN_IP_STR - 1] = '\0';
+                ps = pe;
+
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(",", pe);
+
+                ss = pRespStr->substr(ps, pe - ps);
+                strncpy(s->mask, ss.c_str(), MAX_LEN_IP_STR);
+                s->mask[MAX_LEN_IP_STR - 1] = '\0';
+                ps = pe + 1;
+
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(",", pe);
+
+                ss = pRespStr->substr(ps, pe - ps);
+                strncpy(s->gateway, ss.c_str(), MAX_LEN_IP_STR);
+                s->gateway[MAX_LEN_IP_STR - 1] = '\0';
+                ps = pe + 1;
+
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(",", pe);
+
+
+                ss = pRespStr->substr(ps, pe - ps);
+                strncpy(s->dnsPrimary, ss.c_str(), MAX_LEN_IP_STR);
+                s->dnsPrimary[MAX_LEN_IP_STR - 1] = '\0';
+                ps = pe + 1;
+
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(".", pe);
+                if (pe == string::npos)
+                    return (false);
+                else
+                    pe += 1;
+                pe = pRespStr->find(",", pe);
+
+
+                ss = pRespStr->substr(ps, pe - ps);
+                strncpy(s->dnsSecondary, ss.c_str(), MAX_LEN_IP_STR);
+                s->dnsSecondary[MAX_LEN_IP_STR - 1] = '\0';
+                
+                dbgPuts("~~~~~~~~~~ WNC IP Stats ~~~~~~~~~~~~");
+                dbgPuts("ip: ", false);      dbgPutsNoTime(s->ip);
+                dbgPuts("mask: ", false);    dbgPutsNoTime(s->mask);
+                dbgPuts("gateway: ", false); dbgPutsNoTime(s->gateway);
+                dbgPuts("dns pri: ", false); dbgPutsNoTime(s->dnsPrimary);
+                dbgPuts("dns sec: ", false); dbgPutsNoTime(s->dnsSecondary);
+                dbgPuts("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+            
+                return (true);
+            }
+        }
+    }
+
+    return (false);
+}
+
+bool WncController::deleteSMSTextFromMem(char msgIdx)
+{
+    const char * err = "deleteSMSTextFromMem: Failed!";
+    
+    switch (msgIdx)
+    {
+        case '*':
+            at_deleteSMSTextFromMem_wnc('1');
+            at_deleteSMSTextFromMem_wnc('2');
+            at_deleteSMSTextFromMem_wnc('3');
+            return (true); // WNC may error if slot empty, just ignore!
+
+        case '1':
+        case '2':
+        case '3':
+            if (true == at_deleteSMSTextFromMem_wnc(msgIdx))
+                return (true);
+            else {
+                dbgPuts(err);
+                return (false);
+            }
+
+        default:
+            dbgPuts(err);
+            return (false);
+    }
+}
+
+bool WncController::sendSMSTextFromMem(char msgIdx)
+{
+    const char * err = "deleteSMSTextFromMem: Failed!";
+    
+    switch (msgIdx)
+    {
+        case '*':
+            at_sendSMStextMem_wnc('1');
+            at_sendSMStextMem_wnc('2');
+            at_sendSMStextMem_wnc('3');
+            return (true); // WNC may error if slot is empty, just ignore!
+
+        case '1':
+        case '2':
+        case '3':
+            if (at_sendSMStextMem_wnc(msgIdx) == true)
+                return (true);
+            else {
+                dbgPuts(err);
+                return (false);
+            }
+
+        default:
+            dbgPuts(err);
+            return (false);
+    }
+}
+
+bool WncController::at_deleteSMSTextFromMem_wnc(char n)
+{
+    string cmdStr, respStr;
+    // Message is stored in WNC, now send it!
+    cmdStr = "AT+CMGD=";
+    cmdStr += n;
+    cmdStr += "\r\n";
+    dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str(), false);
+    AtCmdErr_e r = mdmSendAtCmdRsp(cmdStr.c_str(), m_sCmdTimeoutMs, &respStr);
+    dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
+    return (r == WNC_AT_CMD_OK);
+}
+
+bool WncController::at_sendSMStextMem_wnc(char n)
+{
+    string cmdStr, respStr;
+    // Message is stored in WNC, now send it!
+    cmdStr = "AT+CMSS=";
+    cmdStr += n;
+    cmdStr += "\r\n";
+    dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str(), false);
+    AtCmdErr_e r = mdmSendAtCmdRsp(cmdStr.c_str(), m_sCmdTimeoutMs, &respStr);
+    dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
+    return (r == WNC_AT_CMD_OK);
+}    
+
+bool WncController::at_sendSMStext_wnc(const char * const phoneNum, const char * const text)
+{
+    string respStr;
+    string * pRespStr;
+    size_t l = strlen(text);
+    
+    if (l <= MAX_WNC_SMS_LENGTH)
+    {
+        // Check to see if the SMS service is available
+        checkCellLink();
+        if (m_sReadyForSMS == true) {
+            at_send_wnc_cmd("AT+CMGF=1", &pRespStr, m_sCmdTimeoutMs);
+            string cmdStr("AT+CMGS=\"");
+            cmdStr += phoneNum;
+            cmdStr += "\"";
+            dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str());
+            cmdStr += "\x0d"; // x0d = <ENTER>
+            // Send raw command with short timeout (the timeout will fail cause the WNC is not supposed to reply yet!
+            // And we want a delay before sending the actual text part of the string!
+            mdmSendAtCmdRsp(cmdStr.c_str(), 300, &respStr, false);  //  False turns off auto-addition of CR+LF (the WNC wants nothing here)
+            dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
+            if ((respStr.size() > 0) && (respStr.find("ERROR") == string::npos)) {
+                // Part 2 of the text, this is the actual text part:
+                cmdStr = text;
+                dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str());
+                cmdStr += "\x1A";  // <CTRL>-Z is what tells the WNC the message is complete to send!
+                AtCmdErr_e r = mdmSendAtCmdRsp(cmdStr.c_str(), 10000, &respStr);
+                dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
+                if (respStr.size() == 0)
+                    return (false);
+                else
+                    return (r == WNC_AT_CMD_OK);
+            }
+        }
+    }
+
+    return (false);
+}
+
+bool WncController::saveSMSText(const char * const phoneNum, const char * const text, char * msgIdx)
+{
+    if (at_saveSMStext_wnc(phoneNum, text, msgIdx) == true)
+        return (true);
+    else {
+        dbgPuts("saveSMSTextToMem: failed!\r\n");
+        return (false);
+    }
+}
+
+bool WncController::at_saveSMStext_wnc(const char * const phoneNum, const char * const text, char * msgIdx)
+{
+    string respStr;
+    size_t l = strlen(text);
+    
+    if (l <= MAX_WNC_SMS_LENGTH)
+    {
+        // Check to see if the SMS service is available
+        checkCellLink();
+        if (m_sReadyForSMS == true) {
+            string cmdStr("AT+CMGW=\"");
+            cmdStr += phoneNum;
+            cmdStr += "\"";
+            dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str());
+            cmdStr += "\x0d"; // x0d = <ENTER>
+            // Send raw command with short timeout (the timeout will fail cause the WNC is not supposed to reply yet!
+            // And we want a delay before sending the actual text part of the string!
+            mdmSendAtCmdRsp(cmdStr.c_str(), 300, &respStr, false);  //  False turns off auto-addition of CR+LF (the WNC wants nothing here)
+            dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
+            if ((respStr.size() > 0) && (respStr.find("ERROR") == string::npos)) {
+                // Part 2 of the text, this is the actual text part:
+                cmdStr = text;
+                dbgPuts("TX: ", false); dbgPutsNoTime(cmdStr.c_str());
+                cmdStr += "\x1A";  // <CTRL>-Z is what tells the WNC the message is complete to save!
+                AtCmdErr_e r = mdmSendAtCmdRsp(cmdStr.c_str(), 10000, &respStr);
+                dbgPuts("RX: ", false); dbgPutsNoTime(respStr.c_str());
+                if (respStr.size() > 0) {
+                    // respStr will have the SMS index
+                    size_t pos1 = respStr.find("+CMGW: ");
+                    size_t pos2 = respStr.rfind("OK");
+                    if (pos1 != string::npos && pos2 != string::npos) {
+                        *msgIdx = *string(respStr.substr(pos1+7, 1)).c_str();
+                        return (true);
+                    }
+                    else {
+                        *msgIdx = '!';
+                    }
+                }
+            }
+        }
+    }
+        
+    return (false);
+}
+
+bool WncController::at_readSMSlog_wnc(string ** log)
+{
+    return (at_send_wnc_cmd("AT+CMGL", log, m_sCmdTimeoutMs) == WNC_AT_CMD_OK);
+}
+
+size_t WncController::at_readSMStext_wnc(const char n, const char ** log)
+{
+    static string smsReadTxtStr;
+    string * pRespStr;
+    string cmdStr;
+        
+    smsReadTxtStr.erase();
+    cmdStr = "AT+CMGR";
+    cmdStr += '1';
+    if (at_send_wnc_cmd("AT+CMGR", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK)
+        *log = pRespStr->c_str();
+    else
+        *log = "\0";
+        
+    return (pRespStr->size());
+}
+
+bool WncController::at_at_wnc(void)
+{
+    string * pRespStr;
+    return (WNC_AT_CMD_OK == at_send_wnc_cmd("AT", &pRespStr, WNC_QUICK_CMD_TIMEOUT_MS)); // Heartbeat?
+}
+
+bool WncController::at_init_wnc(bool hardReset)
+{
+  string * pRespStr;
+  AtCmdErr_e cmdRes;
+  
+  if (hardReset == true)
+      dbgPuts("Hard Soft Reset!");
+  
+  dbgPuts("Start AT init of WNC:");
+  
+  // Kick it twice to perhaps remove cued responses from an incomplete
+  //  power cycle.
+  at_send_wnc_cmd("AT", &pRespStr, WNC_QUICK_CMD_TIMEOUT_MS);
+  at_send_wnc_cmd("AT", &pRespStr, WNC_QUICK_CMD_TIMEOUT_MS);
+  
+  // Dump the firmware revision on the debug log:
+  at_send_wnc_cmd("AT+GMR", &pRespStr, m_sCmdTimeoutMs);
+
+  // Quick commands below do not need to check cellular connectivity
+  at_send_wnc_cmd("ATE0", &pRespStr, WNC_QUICK_CMD_TIMEOUT_MS);  // Echo Off
+  at_send_wnc_cmd("AT+CMEE=2", &pRespStr, m_sCmdTimeoutMs);      // 2 - verbose error, 1 - numeric error, 0 - just ERROR
+
+  // Setup 3 memory slots in the WNC SIM for SMS usage.
+  at_send_wnc_cmd("AT+CMGF=1", &pRespStr, m_sCmdTimeoutMs);
+  at_send_wnc_cmd("AT+CPMS=\"SM\",\"SM\",\"SM\"", &pRespStr, m_sCmdTimeoutMs);
+
+  cmdRes = at_send_wnc_cmd("AT", &pRespStr, WNC_QUICK_CMD_TIMEOUT_MS);     // Heartbeat?
+  
+  // If the simple commands are not working, no chance of more complex.
+  //  I have seen re-trying commands make it worse.
+  if (cmdRes != WNC_AT_CMD_OK)
+      return (false);
+  
+  cmdRes = at_send_wnc_cmd("AT@INTERNET=1", &pRespStr, m_sCmdTimeoutMs);
+  if (cmdRes != WNC_AT_CMD_OK)
+      return (false);
+  
+  cmdRes = at_send_wnc_cmd("AT@SOCKDIAL=1", &pRespStr, m_sCmdTimeoutMs);
+  if (cmdRes != WNC_AT_CMD_OK)
+      return (false);
+  
+  dbgPuts("SUCCESS: AT init of WNC!");
+  
+  return (true);
+}
+
+int16_t WncController::at_sockopen_wnc(const char * const ip, uint16_t port, uint16_t numSock, bool tcp, uint16_t timeOutSec)
+{
+    string * pRespStr;
+    string cmd_str("AT@SOCKCREAT=");
+    AtCmdErr_e res;
+
+    if (tcp) cmd_str += "1";  // TCP
+    else cmd_str += "2";      // else UDP
+
+    cmd_str += ",0";
+    res = sendWncCmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
+    if (res == WNC_AT_CMD_OK && pRespStr->size() > 0)
+    {
+        size_t pos1 = pRespStr->find("T:");
+        size_t pos2 = pRespStr->rfind("OK");
+        if ((pos1 != string::npos) && (pos2 != string::npos)) {
+            size_t numLen = pos2 - (pos1 + 2);
+            string sockStr = pRespStr->substr(pos1 + 2, numLen);
+            cmd_str = "AT@SOCKCONN=";
+            cmd_str += sockStr;
+            cmd_str += ",\"";
+            cmd_str += ip;
+            cmd_str += "\",";
+            cmd_str += _to_string(port);
+            cmd_str += ",";
+            if (timeOutSec < 30)
+                timeOutSec = 30;
+            else if (timeOutSec > 360)
+                timeOutSec = 360;
+            cmd_str += _to_string(timeOutSec);
+            res = sendWncCmd(cmd_str.c_str(), &pRespStr, 1000 * timeOutSec + 1000);
+            if (m_sMoreDebugEnabled) {
+                at_send_wnc_cmd("AT@SOCKCREAT?", &pRespStr, m_sCmdTimeoutMs);
+                at_send_wnc_cmd("AT@SOCKCONN?", &pRespStr, m_sCmdTimeoutMs);
+            }
+            return (strtol(sockStr.c_str(), NULL, 10));
+        }
+        else {
+            dbgPuts("Invalid sockcreat response!");
+            return (0);
+        }
+    }
+    else
+        return (0);
+}
+
+bool WncController::at_sockclose_wnc(uint16_t numSock)
+{
+    string * pRespStr;
+    string cmd_str("AT@SOCKCLOSE=");
+
+    cmd_str += _to_string(numSock);
+ 
+    // Don't check the cell status to close the socket
+    AtCmdErr_e res = at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
+    
+    if ((res != WNC_AT_CMD_TIMEOUT) && (res != WNC_AT_CMD_OK)) {
+        for (unsigned i = 0; i < WNC_SOCK_CLOSE_RETRY_CNT; i++) {
+            res = at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
+            if ((res == WNC_AT_CMD_TIMEOUT) || (res == WNC_AT_CMD_OK))
+                break;
+        }
+    }
+    
+    return (res == WNC_AT_CMD_OK); 
+}
+
+bool WncController::at_dnsresolve_wnc(const char * s, string * ipStr)
+{
+    string * pRespStr;
+    string str(s);
+    AtCmdErr_e r;
+
+    ipStr->erase(); // Clear out string until resolved!
+    str = "AT@DNSRESVDON=\"" + str;
+    str += "\"";
+    r = sendWncCmd(str.c_str(), &pRespStr, WNC_DNS_RESOLVE_WAIT_MS);
+    if (r == WNC_AT_CMD_OK && pRespStr->size() > 0) {
+        size_t pos_start = pRespStr->find(":\"") + 2;
+        if (pos_start !=  string::npos) {
+            size_t pos_end = pRespStr->find("\"", pos_start) - 1;
+            if (pos_end != string::npos) {
+                if (pos_end > pos_start) {
+                    // Make a copy for use later (the source string is re-used)
+                    *ipStr = pRespStr->substr(pos_start, pos_end - pos_start + 1);
+                    return (true);
+                }
+            }
+        }
+    }
+
+    *ipStr = INVALID_IP_STR;
+
+    return (false);
+}
+
+bool WncController::waitForPowerOnModemToRespond(uint8_t timeoutSecs)
+{
+    // Now, give the modem x seconds to start responding by
+    // sending simple 'AT' commands to modem once per second.
+    if (timeoutSecs > 0) {
+        do {
+            timeoutSecs--;
+            dbgPutsNoTime("\rWaiting ", false); dbgPutsNoTime(_to_string(timeoutSecs), false);
+            dbgPutsNoTime(" ", false);
+            AtCmdErr_e rc = mdmSendAtCmdRsp("AT", 500, &m_sWncStr);
+            if (rc == WNC_AT_CMD_OK) {
+                dbgPutsNoTime("");  // CR LF
+                return true; //timer.read();
+            }
+            waitMs(500);
+        }
+        while (timeoutSecs > 0);    
+        dbgPutsNoTime(""); // CR LF
+    }
+    
+    return (false);
+}
+
+WncController::AtCmdErr_e WncController::at_sockwrite_wnc(const uint8_t * s, uint16_t n, uint16_t numSock, bool isTcp)
+{
+    AtCmdErr_e result;
+
+    if ((n > 0) && (n <= MAX_WNC_WRITE_BYTES)) {
+        string * pRespStr;
+        const char * num2str;
+        string cmd_str;
+
+        if (isTcp == true)
+            cmd_str="AT@SOCKWRITE=";
+        else
+            cmd_str="AT@SOCKWRITE="; // "AT@SOCKSEND=";
+
+        cmd_str += _to_string(numSock);
+        cmd_str += ",";
+        cmd_str += _to_string(n);
+        cmd_str += ",\"";
+        while(n > 0) {
+            n--;
+            num2str = _to_hex_string(*s++);
+            // Always 2-digit ascii hex:
+            if (num2str[1] == '\0')
+                cmd_str += '0';
+            cmd_str += num2str;
+        }
+        cmd_str += "\"";
+        result = sendWncCmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
+    }
+    else {
+        dbgPuts("sockwrite Err, string len bad!");
+        result = WNC_AT_CMD_ERR;
+    }
+    
+    return (result);
+}
+
+WncController::AtCmdErr_e WncController::at_sockread_wnc(string * pS, uint16_t numSock, bool isTcp)
+{
+    AtCmdErr_e result = WNC_AT_CMD_OK;
+
+    string * pRespStr;
+    string cmd_str;
+    size_t pos_start, pos_end;
+    int i;
+    
+    pS->erase();  // Start with a fresh string
+
+    if (isTcp == true)
+        cmd_str="AT@SOCKREAD=";
+    else
+        cmd_str="AT@SOCKREAD="; // "AT@SOCKRECV=";
+
+    cmd_str += _to_string(numSock);
+    cmd_str += ",";
+    cmd_str += _to_string(MAX_WNC_READ_BYTES);
+            
+    // Experimental: read should not need to check cell net status
+    result = at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
+    if (result == WNC_AT_CMD_OK) {
+        if (pRespStr->size() > 0) {
+            pos_start = pRespStr->find("\"");
+            pos_end   = pRespStr->rfind("\"");
+            // Make sure search finds what it's looking for!
+            if (pos_start != string::npos && pos_end != string::npos) {
+                pos_start++;
+                i = pos_end - pos_start;  // Num hex chars, 2 per byte
+            }
+            else
+                i = 0;
+        }
+        else
+            i = 0;
+            
+        if ((i < 0) || ((i % 2) == 1))
+            dbgPuts("Invalid READ string!");
+        
+        if (i > 2*MAX_WNC_READ_BYTES) {
+            i = 2*MAX_WNC_READ_BYTES;
+            dbgPuts("DANGER WNC read data does not match length!");
+        }
+            
+        // If data, convert the hex string into byte values
+        while (i > 0) {
+            i -= 2;
+            *pS += (uint8_t)strtol(pRespStr->substr(pos_start, 2).c_str(), NULL, 16);
+            pos_start += 2;
+        }
+    }
+
+    return (result);
+}
+
+WncController::AtCmdErr_e WncController::at_sockread_wnc(uint8_t * pS, uint16_t * numRead, uint16_t n, uint16_t numSock, bool isTcp)
+{
+    AtCmdErr_e result = WNC_AT_CMD_OK;
+    *numRead = 0;
+    
+    if ((n > 0) && (n <= MAX_WNC_READ_BYTES)) {
+        string * pRespStr;
+        string cmd_str;
+        size_t pos_start, pos_end;
+        int i;
+
+        if (isTcp == true)
+            cmd_str="AT@SOCKREAD=";
+        else
+            cmd_str="AT@SOCKREAD="; // "AT@SOCKRECV=";
+
+        cmd_str += _to_string(numSock);
+        cmd_str += ",";
+        cmd_str += _to_string(n);
+            
+        // Experimental: read should not need to check cell net status
+        result = at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs);
+        if (result == WNC_AT_CMD_OK) {
+            if (pRespStr->size() > 0) {
+                pos_start = pRespStr->find("\"");
+                pos_end   = pRespStr->rfind("\"");
+                // Make sure search finds what it's looking for!
+                if (pos_start != string::npos && pos_end != string::npos) {
+                    pos_start++;
+                    i = pos_end - pos_start;  // Num hex chars, 2 per byte
+                }
+                else
+                    i = 0;
+            }
+            else
+                i = 0;
+                
+            if ((i < 0) || ((i % 2) == 1))
+                dbgPuts("Invalid READ string!");
+                
+            if (i > 2*n) {
+                // Bound the ill formated WNC read string!
+                i = 2*n;
+                dbgPuts("TRUNCATING read data!");
+            }
+
+            // If data, convert the hex string into byte values
+            i /= 2;
+            *numRead = i;
+            while (i > 0) {
+                i--;
+                *pS++ = (uint8_t)strtol(pRespStr->substr(pos_start, 2).c_str(), NULL, 16);
+                pos_start += 2;
+            }
+        }
+    }
+    else {
+        dbgPuts("sockread Err, to many to read!");
+        result = WNC_AT_CMD_ERR;
+    }
+
+    return (result);
+}
+
+bool WncController::at_reinitialize_mdm(void)
+{
+     // Atempt to re-register
+//     string * pRespStr;
+//     dbgPuts("Force re-register!");
+//     at_send_wnc_cmd("AT+CFUN=0,0", &pRespStr, m_sCmdTimeoutMs);
+//     waitMs(31000);
+//     at_send_wnc_cmd("AT+CFUN=1,0", &pRespStr, m_sCmdTimeoutMs);
+//     waitMs(31000);
+    
+    // Initialize the modem
+    dbgPuts("Modem RE-initializing with SOFT Reset...");
+
+    string * pRespStr;
+    at_send_wnc_cmd("AT@DMREBOOT", &pRespStr, m_sCmdTimeoutMs);
+    waitMs(5000);
+
+    // Now, give the modem time to start responding by
+    // sending simple 'AT' commands to the modem once per second.
+    int timeoutSecs = WNC_REINIT_MAX_TIME_MS;
+    do {
+        dbgPuts("\rWaiting ", false); dbgPutsNoTime(_to_string(timeoutSecs), false);
+        AtCmdErr_e rc = mdmSendAtCmdRsp("AT", 500, &m_sWncStr);
+        if (rc == WNC_AT_CMD_OK) {
+            dbgPutsNoTime("");  // CR LF
+            break;
+        }
+        waitMs(500);
+        timeoutSecs--;
+    }
+    while (timeoutSecs > 0);    
+    
+    if (timeoutSecs <= 0)
+        dbgPuts("\r\nModem RE-init FAILED!");
+    else
+        dbgPuts("\r\nModem RE-init complete!");
+        
+    return (timeoutSecs > 0);
+}
+
+WncController::AtCmdErr_e WncController::mdmSendAtCmdRsp(const char *cmd, int timeout_ms, string * rsp, bool crLf)
+{
+    rsp->erase(); // Clean up from possible prior cmd response
+
+    // Don't bother the WNC if user hasn't turned it on.
+    if (m_sState == WNC_OFF)
+        return (WNC_AT_CMD_WNC_NOT_ON);
+        
+    size_t n = strlen(cmd);
+    
+    // Wait per WNC advise
+    waitMs(WNC_WAIT_FOR_AT_CMD_MS);
+ 
+    if (cmd && n > 0) {
+        sendCmd(cmd, crLf);
+//        sendCmd(cmd, n, 1000, crLf);  // 3rd arg is micro seconds between chars sent
+    }
+
+    startTimerA();
+    while (getTimerTicksA_mS() < timeout_ms) {
+        n = mdmGetline(rsp, timeout_ms - getTimerTicksA_mS());
+
+        if (n == 0)
+            continue;
+        
+        if (rsp->rfind("OK") != string::npos) {
+            stopTimerA();
+            return (WNC_AT_CMD_OK);
+        }
+        
+        if (rsp->rfind("+CME ERROR") != string::npos) {
+            stopTimerA();
+            return (WNC_AT_CMD_ERRCME);
+        }
+        
+        if (rsp->rfind("@EXTERR") != string::npos) {
+            stopTimerA();
+            return (WNC_AT_CMD_ERREXT);
+        }
+            
+        if (rsp->rfind("ERROR") != string::npos) {
+            stopTimerA();
+            return (WNC_AT_CMD_ERR);
+        }
+    }
+    stopTimerA();
+    
+    return (WNC_AT_CMD_TIMEOUT);
+}
+
+bool WncController::at_setapn_wnc(const char * const apnStr)
+{
+    string * pRespStr;
+    
+    string cmd_str("AT%PDNSET=1,");
+    cmd_str += apnStr;
+    cmd_str += ",IP";
+    if (WNC_AT_CMD_OK == at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, WNC_APNSET_TIMEOUT_MS))  // Set APN, cmd seems to take a little longer sometimes
+        return (true);
+    else
+        return (false);
+}
+
+bool WncController::at_getrssiber_wnc(int16_t * dBm, int16_t * ber)
+{
+    string * pRespStr;
+    AtCmdErr_e cmdRes;    
+    cmdRes = at_send_wnc_cmd("AT+CSQ", &pRespStr, m_sCmdTimeoutMs);       // Check RSSI,BER
+    if (cmdRes != WNC_AT_CMD_OK)
+        return (false);
+    
+    if (pRespStr->size() == 0) {
+        dbgPuts("Strange RSSI result!");
+        return (false);
+    }
+    else {
+        size_t pos1 = pRespStr->find("SQ:");
+        size_t pos2 = pRespStr->rfind(",");
+        // Sanity check
+        if ((pos1 != string::npos) && (pos2 != string::npos) && (pos2 > pos1)) {
+            string subStr = pRespStr->substr(pos1 + 4, pos2 - pos1 );
+            int rawRssi = atoi(subStr.c_str());
+        
+            // Convert WNC RSSI into dBm range:
+            //  0 - -113 dBm
+            //  1 - -111 dBm
+            //  2..30 - -109 to -53 dBm
+            //  31 - -51dBm or >
+            //  99 - not known or not detectable
+            if (rawRssi == 99)
+                *dBm = -199;
+            else if (rawRssi == 0)
+                *dBm = -113;
+            else if (rawRssi == 1)
+                *dBm = -111;
+            else if (rawRssi == 31)
+                *dBm = -51;
+            else if (rawRssi >= 2 && rawRssi <= 30)
+                *dBm = -113 + 2 * rawRssi;
+            else {
+                dbgPuts("Invalid RSSI!");
+                return (false);
+            }
+            // Parse out BER: 0..7 as RXQUAL values in the table 3GPP TS 45.008 subclause 8.2.4
+            //                99 - unknown or undetectable
+            subStr = pRespStr->substr(pos2 + 1, pRespStr->length() - (pos2 + 1));
+            *ber = atoi(subStr.c_str());
+        }
+        else {
+            dbgPuts("Strange RSSI result2!");
+            return (false);
+        }
+    }
+    
+    return (true);
+}
+
+bool WncController::checkCellLink(void)
+{
+    string * pRespStr;
+    size_t pos;
+    int regSts;
+    int cmdRes1, cmdRes2;
+
+    if (m_sState == WNC_OFF)
+        return (false);
+    
+    m_sState = WNC_ON_NO_CELL_LINK;
+
+    if (m_sMoreDebugEnabled)
+        dbgPuts("<-------- Begin Cell Status ------------");
+
+    cmdRes1 = at_send_wnc_cmd("AT+CSQ", &pRespStr, m_sCmdTimeoutMs);       // Check RSSI,BER
+
+    // If no response, don't bother with more commands
+    if (cmdRes1 != WNC_AT_CMD_TIMEOUT)
+        cmdRes2 = at_send_wnc_cmd("AT+CPIN?", &pRespStr, m_sCmdTimeoutMs);     // Check if SIM locked
+    else {
+        if (m_sMoreDebugEnabled)
+            dbgPuts("------------ WNC No Response! --------->");
+
+        return (false);
+    }
+    
+    if ((cmdRes1 != WNC_AT_CMD_OK) || (cmdRes2 != WNC_AT_CMD_OK) || (pRespStr->size() == 0))
+    {
+        if (m_sMoreDebugEnabled)
+        {
+            if ((cmdRes1 == WNC_AT_CMD_TIMEOUT) || (cmdRes2 == WNC_AT_CMD_TIMEOUT))
+                dbgPuts("------------ WNC No Response! --------->");
+            else
+                dbgPuts("------------ WNC Cmd Error! ----------->");
+        }
+        
+        // If by a miracle it responds to the 2nd after the 1st, keep going
+        if ((cmdRes2 == WNC_AT_CMD_TIMEOUT) || (pRespStr->size() == 0))
+            return (false);      
+    }
+  
+    // If SIM Card not ready don't bother with commands!
+    if (pRespStr->find("CPIN: READY") == string::npos)
+    {
+        if (m_sMoreDebugEnabled)
+            dbgPuts("------------ WNC SIM Problem! --------->");
+
+        return (false);
+    }
+
+    // SIM card OK, now check for signal and cellular network registration
+    cmdRes1 = at_send_wnc_cmd("AT+CREG?", &pRespStr, m_sCmdTimeoutMs);      // Check if registered on network
+    if (cmdRes1 != WNC_AT_CMD_OK || pRespStr->size() == 0)
+    {
+        if (m_sMoreDebugEnabled)
+            dbgPuts("------------ WNC +CREG? Fail! --------->");
+
+        return (false);
+    }
+    else
+    {
+        pos = pRespStr->find("CREG: ");
+        if (pos != string::npos)
+        {
+            // The registration is the 2nd arg in the comma separated list
+            *pRespStr = pRespStr->substr(pos+8, 1);
+            regSts = atoi(pRespStr->c_str());
+            switch (regSts) {
+                case 1:
+                case 5:
+                case 6:
+                case 7:
+                    m_sReadyForSMS = true;
+                    break;
+                default:
+                    m_sReadyForSMS = false;
+                    dbgPuts("SMS Service Down!");
+            }
+
+            // 1 - registered home, 5 - registered roaming
+            if ((regSts != 1) && (regSts != 5))
+            {
+                if (m_sMoreDebugEnabled)
+                    dbgPuts("------ WNC Cell Link Down for Data! --->");
+
+                return (false);
+            }
+        }
+
+        if (m_sMoreDebugEnabled)
+            dbgPuts("------------ WNC Ready ---------------->");
+    }
+    
+    // If we made it this far and the WNC did respond, keep the ON state
+    if (m_sState != WNC_NO_RESPONSE)
+        m_sState = WNC_ON;
+    
+    return (true);
+}
+
+int WncController::dbgPutsNoTime(const char * s, bool crlf)
+{
+    if (m_sDebugEnabled == true) {
+        int r = dbgWriteChars(s);
+        if (crlf == true)
+            return (dbgWriteChars("\r\n"));
+        else
+            return (r);
+    }
+    else
+        return 0;
+};
+
+int WncController::dbgPuts(const char * s, bool crlf)
+{
+    dbgPutsNoTime("[*] ", false);
+    dbgPutsNoTime(_to_string(getLogTimerTicks()), false);
+    dbgPutsNoTime(" ", false);
+
+    int r = dbgPutsNoTime(s, false);
+    
+    if (crlf == true)
+        return (dbgPutsNoTime("", true));
+    else
+        return (r);
+};
+    
+void WncController::sendCmd(const char * cmd, bool crLf)
+{
+    puts(cmd);
+    if (crLf == true)
+        puts("\r\n");
+}
+
+void WncController::sendCmd(const char * cmd, unsigned n, unsigned wait_uS, bool crLf)
+{
+    while (n--) {
+        putc(*cmd++);
+        waitUs(wait_uS);
+    };
+    if (crLf == true) {
+        putc('\r');
+        waitUs(wait_uS);
+        putc('\n');
+        waitUs(wait_uS);
+    }
+}
+
+}; // End namespace WncController_fk
+
--- a/WncController.h	Thu Mar 09 00:58:50 2017 +0000
+++ b/WncController.h	Thu Apr 06 21:42:30 2017 -0400
@@ -1,624 +1,625 @@
-/** 
-    Copyright (c) 2016 Fred Kellerman
- 
-    Permission is hereby granted, free of charge, to any person obtaining a copy
-    of this software and associated documentation files (the "Software"), to deal
-    in the Software without restriction, including without limitation the rights
-    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-    copies of the Software, and to permit persons to whom the Software is
-    furnished to do so, subject to the following conditions:
- 
-    The above copyright notice and this permission notice shall be included in
-    all copies or substantial portions of the Software.
- 
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-    THE SOFTWARE.
-    
-    @file          WncController.h
-    @purpose       Controls WNC Cellular Modem
-    @version       1.0
-    @date          July 2016
-    @author        Fred Kellerman
-    
-    Notes: This code originates from the following mbed repository:
-    
-    https://developer.mbed.org/teams/Avnet/code/WncControllerLibrary/
-*/
-
-
-#ifndef __WNCCONTROLLER_H_
-#define __WNCCONTROLLER_H_
-
-#include <string>
-#include <stdint.h>
-
-namespace WncController_fk {
-
-using namespace std;
-
-/** @defgroup API The WncControllerLibrary API */
-/** @defgroup MISC Misc WncControllerLibrary functions */
-/** @defgroup INTERNALS WncControllerLibrary Internals */
-
-static const uint8_t  MAX_LEN_IP_STR = 16;         // Length includes room for the extra NULL
-
-/** \brief  Contains info fields for the WNC Internet Attributes */
-struct WncIpStats
-{
-    string wncMAC;
-    char ip[MAX_LEN_IP_STR];
-    char mask[MAX_LEN_IP_STR];
-    char gateway[MAX_LEN_IP_STR];
-    char dnsPrimary[MAX_LEN_IP_STR];
-    char dnsSecondary[MAX_LEN_IP_STR];
-};
-
-
-/**
- * @author Fred Kellerman
- * @see API 
- *
- * <b>WncController</b> This mbed C++ class is for controlling the WNC
- *  Cellular modem via the serial AT command interface.  This was
- *  developed with respect to version 1.3 of the WNC authored
- *  unpublished spec.  This class is only designed to have 1 instantiation,
- *  it is also not multi-thread safe.  There are no OS specific
- *  entities being used, there are pure virtual methods that an 
- *  inheriting class must fulfill.  That inheriting class will have
- *  OS and platform specific entities.  See WncControllerK64F for an
- *  example for the NXP K64F Freedom board.
- */
-class WncController
-{
-public:
-
-    static const unsigned MAX_NUM_WNC_SOCKETS = 5;  // Max number of simultaneous sockets that the WNC supports
-    static const unsigned MAX_POWERUP_TIMEOUT = 60; // How long the powerUp method will try to turn on the WNC Shield
-                                                    //  (this is the default if the user does not over-ride on power-up
-
-    /** Tracks mode of the WNC Shield hardware */
-    enum WncState_e {
-        WNC_OFF = 0,
-        WNC_ON, // This is intended to mean all systems go, including cell link up but socket may not be open
-        WNC_ON_NO_CELL_LINK,
-        WNC_NO_RESPONSE
-    };
-
-    /**
-     *
-     * Constructor for WncController class, sets up internals.
-     * @ingroup API
-     * @return none.
-     */
-    WncController(void);
-    
-    /**
-     *
-     * Used internally but also make public for a user of the Class to 
-     * interrogate state as well.
-     * @ingroup API
-     * @return the current state of the Wnc hardware.
-     */
-    WncState_e getWncStatus(void);
-    
-    /**
-     *
-     * Allows a user to set the WNC modem to use the given Cellular APN 
-     * @ingroup API
-     * @param apnStr - a null terminated c-string
-     * @return true if the APN set was succesful, else false
-     */
-    bool setApnName(const char * const apnStr);
-
-    /**
-     *
-     * Queries the WNC modem for the current RX RSSI in units of coded dBm
-     * @ingroup API
-     * @return 0 – -113 dBm or less
-     *         1 – -111 dBm
-     *         2...30 – -109 dBm to –53 dBm
-     *        31 – -51 dBm or greater
-     *        99 – not known or not detectable
-     */
-    int16_t getDbmRssi(void);
-    
-    /**
-     *
-     * Queries the WNC modem for the current Bit Error Rate
-     * @ingroup API
-     * @return 0...7 – as RXQUAL values in the table in 3GPP TS 45.008
-     *            subclause 8.2.4
-     *         99 – not known or not detectable
-     */
-    int16_t get3gBer(void);
-
-    /**
-     *
-     * Powers up the WNC modem
-     * @ingroup API
-     * @param apn - the apn c-string to set the WNC modem to use
-     * @param powerUpTimeoutSecs - the amount of time to wait for the WNC modem to turn on
-     * @return true if powerup was a success, else false.
-     */
-    bool powerWncOn(const char * const apn, uint8_t powerUpTimeoutSecs = MAX_POWERUP_TIMEOUT);
-
-    /**
-     *
-     * Returns the NAT Self, gateway, masks and dns IP
-     * @ingroup API
-     * @param s - a pointer to a struct that will contain the IP info.
-     * @return true if success, else false.
-     */
-    bool getWncNetworkingStats(WncIpStats * s);
-
-    /**
-     *
-     * Takes a text URL and converts it internally to an IP address for the
-     * socket number given.
-     * @ingroup API
-     * @param numSock - The number of the socket to lookup the IP address for.
-     * @param url - a c-string text URL
-     * @return true if success, else false.
-     */
-    bool resolveUrl(uint16_t numSock, const char * url);
-
-    /**
-     *
-     * If you know the IP address you can set the socket up to use it rather
-     * than using a text URL.
-     * @ingroup API
-     * @param numSock - The number of the socket to use the IP address for.
-     * @param ipStr - a c-string text IP addrese like: 192.168.0.1
-     * @return true if success, else false.
-     */
-    bool setIpAddr(uint16_t numSock, const char * ipStr);
-
-    /**
-     *
-     * Opens a socket for the given number, port and IP protocol.  Before
-     * using open, you must use either resolveUrl() or setIpAddr().
-     * @ingroup API
-     * @param numSock - The number of the socket to open.
-     * @param port - the IP port to open
-     * @param tcp - set true for TCP, false for UDP
-     * @param timeoutSec - the amount of time in seconds to wait for the open to complete
-     * @return true if success, else false.
-     */
-    bool openSocket(uint16_t numSock, uint16_t port, bool tcp, uint16_t timeOutSec = 30);
-
-    /**
-     *
-     * Opens a socket for the given text URL, number, port and IP protocol.
-     * @ingroup API
-     * @param numSock - The number of the socket to open.
-     * @param url - a c-string text URL, the one to open a socket for.
-     * @param port - the IP port to open.
-     * @param tcp - set true for TCP, false for UDP.
-     * @param timeoutSec - the amount of time in seconds to wait for the open to complete.
-     * @return true if success, else false.
-     */
-    bool openSocketUrl(uint16_t numSock, const char * url, uint16_t port, bool tcp, uint16_t timeOutSec = 30);
-
-    /**
-     *
-     * Opens a socket for the given text IP address, number, port and IP protocol.
-     * @ingroup API
-     * @param numSock - The number of the socket to open.
-     * @param ipAddr - a c-string text IP address like: "192.168.0.1".
-     * @param port - the IP port to open.
-     * @param tcp - set true for TCP, false for UDP.
-     * @param timeoutSec - the amount of time in seconds to wait for the open to complete.
-     * @return true if success, else false.
-     */
-    bool openSocketIpAddr(uint16_t numSock, const char * ipAddr, uint16_t port, bool tcp, uint16_t timeOutSec = 30);
-
-
-    /**
-     *
-     * Write data bytes to a Socket, the Socket must already be open.
-     * @ingroup API
-     * @param numSock - The number of the socket to open.
-     * @parma s - an array of bytes to write to the socket.
-     * @param n - the number of bytes to write.
-     * @return true if success, else false.
-     */
-     bool write(uint16_t numSock, const uint8_t * s, uint32_t n);
-
-    /**
-     *
-     * Poll to read available data bytes from an already open Socket.  This method
-     * will retry reads to what setReadRetries() sets it to and the delay in between
-     * retries that is set with setReadRetryWait()
-     * @ingroup API
-     * @param numSock - The number of the socket to open.
-     * @parma readBuf - a pointer to where read will put the data.
-     * @param maxReadBufLen - The number of bytes readBuf has room for.
-     * @return the number of bytes actually read into readBuf.  0 is a valid value if no data is available.
-     */
-    size_t read(uint16_t numSock, uint8_t * readBuf, uint32_t maxReadBufLen);
-    
-    /**
-     *
-     * Poll to read available data bytes from an already open Socket.  This method
-     * will retry reads to what setReadRetries() sets it to and the delay in between
-     * retries that is set with setReadRetryWait()
-     * @ingroup API
-     * @param numSock - The number of the socket to open.
-     * @parma readBuf - a pointer to pointer that will be set to point to an internal byte buffer that contains any read data.
-     * @return the number of bytes actually read into the pointer that readBuf points to.  0 is a valid value if no data is available.
-     */
-    size_t read(uint16_t numSock, const uint8_t ** readBuf);
-
-    /**
-     *
-     * Set the number of retries that the read methods will use.  If a read returns 0 data this setting will have the read
-     * re-read to see if new data is available.
-     * @ingroup API
-     * @param numSock - The number of the socket to open.
-     * @parma retries - the number of retries to perform.
-     * @return none.
-     */
-    void setReadRetries(uint16_t numSock, uint16_t retries);
-
-    /**
-     *
-     * Set the time between retires that the read methods will use.  If a read returns 0 data this setting will have the read
-     * re-read and use this amount of delay in between the re-reads.
-     * @ingroup API
-     * @param numSock - The number of the socket to open.
-     * @parma waitMs - the amount of time in mS to wait between retries.
-     * @return none.
-     */
-    void setReadRetryWait(uint16_t numSock, uint16_t waitMs);
-
-    /**
-     *
-     * Closes an already open Socket.
-     * @ingroup API
-     * @param numSock - The number of the socket to open.
-     * @return true if success else false.
-     */
-    bool closeSocket(uint16_t numSock);
-
-    /**
-     *
-     * Sets the amount of time to wait between the raw AT commands that are sent to the WNC modem.
-     * Generally you don't want to use this but it is here just in case.
-     * @ingroup API
-     * @param toMs - num mS to wait between the AT cmds.
-     * @return none.
-     */
-    void setWncCmdTimeout(uint16_t toMs);
-
-    /**
-     *
-     * Gets the IP address of the given socket number.
-     * @ingroup API
-     * @param numSock - The number of the socket to open.
-     * @param myIpAddr - a c-string that contains the socket's IP address.
-     * @return true if success else false.
-     */
-    bool getIpAddr(uint16_t numSock, char myIpAddr[MAX_LEN_IP_STR]);
-    
-    /**
-     *
-     * Enables debug output from this class.
-     * @ingroup API
-     * @param on - true enables debug output, false disables
-     * @param moreDebugOn - true enables verbose debug, false truncates debug output.
-     * @return none.
-     */
-    void enableDebug(bool on, bool moreDebugOn);
-    
-    ///////////////////////////////////////////
-    //  SMS messaging
-    ///////////////////////////////////////////
-
-    static const uint16_t MAX_WNC_SMS_MSG_SLOTS = 3;   // How many SMS messages the WNC can store and receive at a time.
-    static const uint16_t MAX_WNC_SMS_LENGTH    = 160; // The maximum length of a 7-bit SMS message the WNC can send and receive.
-    
-    /** Struct for SMS messages */
-    struct WncSmsInfo
-    {
-        // Content
-        char   idx;
-        string number;
-        string date;
-        string time;
-        string msg;
-        
-        // Attributes
-        bool incoming;
-        bool unsent;
-        bool unread;
-        bool pduMode;
-        bool msgReceipt;
-    };
-
-    /** Struct to contain a list of SMS message structs */
-    struct WncSmsList
-    {
-        uint8_t msgCount;
-        WncSmsInfo e[MAX_WNC_SMS_MSG_SLOTS];
-    };
-
-    /**
-     *
-     * Sends an SMS text message to someone.
-     * @ingroup API
-     * @param phoneNum - c-string 15 digit MSISDN number or ATT Jasper number (standard phone number not supported because ATT IoT SMS does not support it).
-     * @param text - the c-string text to send to someone.
-     * @return true if success else false.
-     */
-    bool sendSMSText(const char * const phoneNum, const char * const text);
-
-    /**
-     *
-     * Incoming messages are stored in a log in the WNC modem, this will read that
-     * log.
-     * @ingroup API
-     * @param log - the log contents if reading it was successful.
-     * @return true if success else false.
-     */
-    bool readSMSLog(struct WncSmsList * log);
-
-    /**
-     *
-     * Incoming messages are stored in a log in the WNC modem, this will read out
-     * messages that are unread and also then mark them read.
-     * @ingroup API
-     * @param w - a list of SMS messages that unread messages will be put into.
-     * @param deleteRead - if a message is read and this is set true the message will be deleted from the WNC modem log.
-     * If it is false the message will remain in the internal log but be marked as read.
-     * @return true if success else false.
-     */
-    bool readUnreadSMSText(struct WncSmsList * w, bool deleteRead = true);
-    
-    /**
-     *
-     * Saves a text message into internal SIM card memory of the WNC modem.
-     * There are only 3 slots available this is for unread, read and saved.
-     * @ingroup API
-     * @param phoneNum - c-string 15 digit MSISDN number or ATT Jasper number (standard phone number not supported because ATT IoT SMS does not support it).
-     * @param text - the c-string text to send to someone.
-     * @param msgIdx - the slot position to save the message: '1', '2', '3'
-     * @return true if success else false.
-     */
-    bool saveSMSText(const char * const phoneNum, const char * const text, char * msgIdx);
-
-    /**
-     *
-     * Sends a prior stored a text message from internal SIM card memory of the WNC modem.
-     * If no messages are stored the behaviour of this method is undefined.
-     * @ingroup API
-     * @param msgIdx - the slot position to save the message: '1', '2', '3'
-     * @return true if success else false.
-     */
-    bool sendSMSTextFromMem(char msgIdx);
-
-    /**
-     *
-     * Deletes a prior stored a text message from internal SIM card memory of the WNC modem.
-     * If no messages are stored the behaviour of this method is undefined.
-     * @ingroup API
-     * @param msgIdx - the slot position to save the message: '1', '2', '3' or '*' deletes them all.
-     * @return true if success else false.
-     */
-    bool deleteSMSTextFromMem(char msgIdx);
-    
-    /**
-     *
-     * Retreives the SIM card ICCID number.
-     * @ingroup API
-     * @param iccid - a pointer to C++ string that contains the retrieved number.
-     * @return true if success else false.
-     */
-    bool getICCID(string * iccid);
-
-    /**
-     *
-     * Converts an ICCID number into a MSISDN number.  The ATT SMS system for IoT only allows use of the 15-digit MSISDN number.
-     * @ingroup API
-     * @param iccid - the number to convert.
-     * @param msisdn - points to a C++ string that has the converted number.
-     * @return true if success else false.
-     */
-    bool convertICCIDtoMSISDN(const string & iccid, string * msisdn);
-    
-    ///////////////////////////////////////////
-    // Neighborhood Cell Info
-    ///////////////////////////////////////////
-    
-    /**
-     *
-     * Fetches the signal quality log from the WNC modem.
-     * @ingroup API
-     * @param log - a pointer to an internal buffer who's contents contain the signal quality metrics.
-     * @return The number of chars in the log.
-     */
-    size_t getSignalQuality(const char ** log);
-    
-    /**  A struct for the WNC modem Date and Time */
-    struct WncDateTime
-    {
-        uint8_t  year;
-        uint8_t  month;
-        uint8_t  day;
-        uint8_t  hour;
-        uint8_t  min;
-        uint8_t  sec;
-    };
-
-    /**
-     *
-     * Fetches the cell tower's time and date.  The time is accurate when read
-     * but significant delays exist between the time it is read and returned.
-     * @ingroup API
-     * @param tod - User supplies a pointer to a tod struct and this method fills it in.
-     * @return true if success else false.
-     */
-    bool getTimeDate(struct WncDateTime * tod);
-    
-    /**
-     *
-     * ICMP Pings a URL, the results are only output to the debug log for now!
-     * @ingroup API
-     * @param url - a c-string whose URL is to be pinged.
-     * @return true if success else false.
-     */
-    bool pingUrl(const char * url);
-
-    /**
-     *
-     * ICMP Pings an IP, the results are only output to the debug log for now!
-     * @ingroup API
-     * @param ip - a c-string whose IP is to be pinged.
-     * @return true if success else false.
-     */
-    bool pingIp(const char * ip);
-    
-    /**
-     *
-     * Allows a user to send a raw AT command to the WNC modem.
-     * @ingroup API
-     * @param cmd - the c-string cmd to send like: "AT"
-     * @param resp - a pointer to the c-string cmd's response.
-     * @param sizeRespBuf - how large the command response buffer is, sets the max response length.
-     * @param ms_timeout - how long to wait for the WNC to respond to your command.
-     * @return the number of characters in the response from the WNC modem.
-     */
-    size_t sendCustomCmd(const char * cmd, char * resp, size_t sizeRespBuf, int ms_timeout);
-
-protected:
-
-    // Debug output methods
-    int dbgPutsNoTime(const char * s, bool crlf = true);
-    int dbgPuts(const char * s, bool crlf = true);
-    const char * _to_string(int64_t value);
-    const char * _to_hex_string(uint8_t value);    
-
-    // Sends commands to WNC via
-    enum AtCmdErr_e {
-        WNC_AT_CMD_OK,
-        WNC_AT_CMD_ERR,
-        WNC_AT_CMD_ERREXT,
-        WNC_AT_CMD_ERRCME,
-        WNC_AT_CMD_INVALID_RESPONSE,
-        WNC_AT_CMD_TIMEOUT,
-        WNC_AT_CMD_NO_CELL_LINK,
-        WNC_AT_CMD_WNC_NOT_ON
-    };
-
-    bool waitForPowerOnModemToRespond(uint8_t powerUpTimeoutSecs);    
-    AtCmdErr_e sendWncCmd(const char * const s, string ** r, int ms_timeout);
-
-    // Users must define these functionalities in the inheriting class:
-        // General I/O and timing:
-    virtual int putc(char c)              = 0;
-    virtual int puts(const char * s)      = 0;
-    virtual char getc(void)               = 0;
-    virtual int charReady(void)           = 0;
-    virtual int dbgWriteChar(char b)      = 0;
-    virtual int dbgWriteChars(const char *b) = 0;
-    virtual void waitMs(int t)            = 0;
-    virtual void waitUs(int t)            = 0;
-    virtual bool initWncModem(uint8_t powerUpTimeoutSecs) = 0;
-    
-        // Isolate OS timers
-    virtual int  getLogTimerTicks(void)  = 0;
-    virtual void startTimerA(void)       = 0;
-    virtual void stopTimerA(void)        = 0;
-    virtual int  getTimerTicksA_mS(void) = 0;
-    virtual void startTimerB(void)       = 0;
-    virtual void stopTimerB(void)        = 0;
-    virtual int  getTimerTicksB_mS(void) = 0;
-    
-private:
-
-    bool softwareInitMdm(void);
-    bool checkCellLink(void);
-    AtCmdErr_e mdmSendAtCmdRsp(const char * cmd, int timeout_ms, string * rsp, bool crLf = true);
-    size_t mdmGetline(string * buff, int timeout_ms);
-    bool at_at_wnc(void);
-    bool at_init_wnc(bool hardReset = false);
-    int16_t at_sockopen_wnc(const char * const ip, uint16_t port, uint16_t numSock, bool tcp, uint16_t timeOutSec);
-    bool at_sockclose_wnc(uint16_t numSock);
-    bool at_dnsresolve_wnc(const char * s, string * ipStr);
-    AtCmdErr_e at_sockwrite_wnc(const uint8_t * s, uint16_t n, uint16_t numSock, bool isTcp);
-    AtCmdErr_e at_sockread_wnc(uint8_t * pS, uint16_t * numRead, uint16_t n, uint16_t numSock, bool isTcp);
-    AtCmdErr_e at_sockread_wnc(string * pS, uint16_t numSock, bool isTcp);
-    bool at_reinitialize_mdm(void);
-    AtCmdErr_e at_send_wnc_cmd(const char * s, string ** r, int ms_timeout);
-    bool at_setapn_wnc(const char * const apnStr);
-    bool at_sendSMStext_wnc(const char * const phoneNum, const char * const text);
-    bool at_get_wnc_net_stats(WncIpStats * s);
-    bool at_readSMSlog_wnc(string ** log);
-    size_t at_readSMStext_wnc(const char ** log);
-    size_t at_readSMStext_wnc(const char n, const char ** log);
-    bool at_getrssiber_wnc(int16_t * dBm, int16_t * ber3g);
-    void closeOpenSocket(uint16_t numSock);
-    bool sockWrite(const uint8_t * const s, uint16_t n, uint16_t numSock, bool isTcp);
-    bool at_sendSMStextMem_wnc(char n);
-    bool at_deleteSMSTextFromMem_wnc(char n);
-    bool at_saveSMStext_wnc(const char * const phoneNum, const char * const text, char * msgIdx);
-    size_t at_getSignalQuality_wnc(const char ** log);
-    bool at_gettimedate_wnc(struct WncDateTime * tod);
-    bool at_ping_wnc(const char * ip);
-    bool at_geticcid_wnc(string * iccid);
-    
-    // Utility methods
-    void sendCmd(const char * cmd, bool crLf);
-    void sendCmd(const char * cmd, unsigned n, unsigned wait_uS, bool crLf);    
-    inline void rx_char_wait(void) {
-        // waitUs(1000);
-    }
-    
-    // Important constants
-    static const uint16_t MAX_WNC_READ_BYTES        = 1500;                            // This bounds the largest amount of data that the WNC read from a socket will return
-    static const uint16_t MAX_WNC_WRITE_BYTES       = MAX_WNC_READ_BYTES;              // This is the largest amount of data that the WNC can write per sockwrite.
-    static const uint16_t MAX_LEN_WNC_CMD_RESPONSE  = (MAX_WNC_READ_BYTES * 2 + 100);  // Max number of text characters in a WNC AT response *2 because bytes are converted into 2 hex-digits +100 for other AT@ chars.
-    static const uint16_t WNC_AUTO_POLL_MS          = 250;   // Sets default (may be overriden with method) poll interval (currently not used, future possible feature.
-    static const uint16_t WNC_CMD_TIMEOUT_MS        = 40000; // Sets default (may be overriden) time that the software waits for an AT response from the WNC.
-    static const uint16_t WNC_QUICK_CMD_TIMEOUT_MS  = 2000;  // Used for simple commands that should immediately respond such as "AT", cmds that are quicker than WNC_CMD_TIMEOUT_MS.
-    static const uint16_t WNC_WAIT_FOR_AT_CMD_MS    = 0;     // Wait this much between multiple in a row AT commands to the WNC.
-    static const uint16_t WNC_SOFT_INIT_RETRY_COUNT = 10;    // How many times the WNC will be tried to revive if it stops responding.
-    static const uint16_t WNC_DNS_RESOLVE_WAIT_MS   = 60000; // How much time to wait for the WNC to respond to a DNS resolve/lookup.
-    static const uint16_t WNC_TRUNC_DEBUG_LENGTH    = 80;    // Always make this an even number, how many chars for the debug output before shortening the debug ouput, this is used when moreDebug = false. 
-    static const uint16_t WNC_APNSET_TIMEOUT_MS     = 60000; // How long to wait for the WNC to respond to setting the APN string.
-    static const uint16_t WNC_PING_CMD_TIMEOUT_MS   = 60000; // Amount of time to wait for the WNC to respond to AT@PINGREQ (with cmd default params for timeout, does not change WNC cmd's timeout) 
-    static const int      WNC_REINIT_MAX_TIME_MS    = 60000; // How long to wait for the WNC to reset after it was already up and running after power-up.
-    static const uint16_t WNC_SOCK_CLOSE_RETRY_CNT  = 3;     // How many times to try to close the socket if the WNC gives an error.
-    static const char * const INVALID_IP_STR;                // Just a string set to an IP address when DNS resolve fails.
-        
-    struct WncSocketInfo_s {
-        int16_t numWncSock;
-        bool open;
-        string myIpAddressStr;
-        uint16_t myPort;
-        uint8_t readRetries;
-        uint16_t readRetryWaitMs;
-        bool isTcp;
-        uint16_t timeOutSec;
-    };
-
-    static WncSocketInfo_s m_sSock[MAX_NUM_WNC_SOCKETS];
-    static const WncSocketInfo_s defaultSockStruct;
-    static WncState_e m_sState;
-    static uint16_t m_sCmdTimeoutMs;
-    static string m_sApnStr;
-    static string m_sWncStr;
-    static uint8_t m_sPowerUpTimeoutSecs;
-    static bool m_sDebugEnabled;
-    static bool m_sMoreDebugEnabled;
-    static bool m_sCheckNetStatus;
-    static bool m_sReadyForSMS;
-};
-
-};  // End namespace WncController_fk
-
-#endif
\ No newline at end of file
+/** 
+    Copyright (c) 2016 Fred Kellerman
+ 
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+ 
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+ 
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+    THE SOFTWARE.
+    
+    @file          WncController.h
+    @purpose       Controls WNC Cellular Modem
+    @version       1.0
+    @date          July 2016
+    @author        Fred Kellerman
+    
+    Notes: This code originates from the following mbed repository:
+    
+    https://developer.mbed.org/teams/Avnet/code/WncControllerLibrary/
+*/
+
+
+#ifndef __WNCCONTROLLER_H_
+#define __WNCCONTROLLER_H_
+
+#include <string>
+#include <stdint.h>
+
+namespace WncController_fk {
+
+using namespace std;
+
+/** @defgroup API The WncControllerLibrary API */
+/** @defgroup MISC Misc WncControllerLibrary functions */
+/** @defgroup INTERNALS WncControllerLibrary Internals */
+
+static const uint8_t  MAX_LEN_IP_STR = 16;         // Length includes room for the extra NULL
+
+/** \brief  Contains info fields for the WNC Internet Attributes */
+struct WncIpStats
+{
+    string wncMAC;
+    char ip[MAX_LEN_IP_STR];
+    char mask[MAX_LEN_IP_STR];
+    char gateway[MAX_LEN_IP_STR];
+    char dnsPrimary[MAX_LEN_IP_STR];
+    char dnsSecondary[MAX_LEN_IP_STR];
+};
+
+
+/**
+ * @author Fred Kellerman
+ * @see API 
+ *
+ * <b>WncController</b> This mbed C++ class is for controlling the WNC
+ *  Cellular modem via the serial AT command interface.  This was
+ *  developed with respect to version 1.3 of the WNC authored
+ *  unpublished spec.  This class is only designed to have 1 instantiation,
+ *  it is also not multi-thread safe.  There are no OS specific
+ *  entities being used, there are pure virtual methods that an 
+ *  inheriting class must fulfill.  That inheriting class will have
+ *  OS and platform specific entities.  See WncControllerK64F for an
+ *  example for the NXP K64F Freedom board.
+ */
+class WncController
+{
+public:
+
+    static const unsigned MAX_NUM_WNC_SOCKETS = 5;  // Max number of simultaneous sockets that the WNC supports
+    static const unsigned MAX_POWERUP_TIMEOUT = 60; // How long the powerUp method will try to turn on the WNC Shield
+                                                    //  (this is the default if the user does not over-ride on power-up
+
+    /** Tracks mode of the WNC Shield hardware */
+    enum WncState_e {
+        WNC_OFF = 0,
+        WNC_ON, // This is intended to mean all systems go, including cell link up but socket may not be open
+        WNC_ON_NO_CELL_LINK,
+        WNC_NO_RESPONSE
+    };
+
+    /**
+     *
+     * Constructor for WncController class, sets up internals.
+     * @ingroup API
+     * @return none.
+     */
+    WncController(void);
+    
+    /**
+     *
+     * Used internally but also make public for a user of the Class to 
+     * interrogate state as well.
+     * @ingroup API
+     * @return the current state of the Wnc hardware.
+     */
+    WncState_e getWncStatus(void);
+    
+    /**
+     *
+     * Allows a user to set the WNC modem to use the given Cellular APN 
+     * @ingroup API
+     * @param apnStr - a null terminated c-string
+     * @return true if the APN set was succesful, else false
+     */
+    bool setApnName(const char * const apnStr);
+
+    /**
+     *
+     * Queries the WNC modem for the current RX RSSI in units of coded dBm
+     * @ingroup API
+     * @return 0 – -113 dBm or less
+     *         1 – -111 dBm
+     *         2...30 – -109 dBm to –53 dBm
+     *        31 – -51 dBm or greater
+     *        99 – not known or not detectable
+     */
+    int16_t getDbmRssi(void);
+    
+    /**
+     *
+     * Queries the WNC modem for the current Bit Error Rate
+     * @ingroup API
+     * @return 0...7 – as RXQUAL values in the table in 3GPP TS 45.008
+     *            subclause 8.2.4
+     *         99 – not known or not detectable
+     */
+    int16_t get3gBer(void);
+
+    /**
+     *
+     * Powers up the WNC modem
+     * @ingroup API
+     * @param apn - the apn c-string to set the WNC modem to use
+     * @param powerUpTimeoutSecs - the amount of time to wait for the WNC modem to turn on
+     * @return true if powerup was a success, else false.
+     */
+    bool powerWncOn(const char * const apn, uint8_t powerUpTimeoutSecs = MAX_POWERUP_TIMEOUT);
+
+    /**
+     *
+     * Returns the NAT Self, gateway, masks and dns IP
+     * @ingroup API
+     * @param s - a pointer to a struct that will contain the IP info.
+     * @return true if success, else false.
+     */
+    bool getWncNetworkingStats(WncIpStats * s);
+
+    /**
+     *
+     * Takes a text URL and converts it internally to an IP address for the
+     * socket number given.
+     * @ingroup API
+     * @param numSock - The number of the socket to lookup the IP address for.
+     * @param url - a c-string text URL
+     * @return true if success, else false.
+     */
+    bool resolveUrl(uint16_t numSock, const char * url);
+
+    /**
+     *
+     * If you know the IP address you can set the socket up to use it rather
+     * than using a text URL.
+     * @ingroup API
+     * @param numSock - The number of the socket to use the IP address for.
+     * @param ipStr - a c-string text IP addrese like: 192.168.0.1
+     * @return true if success, else false.
+     */
+    bool setIpAddr(uint16_t numSock, const char * ipStr);
+
+    /**
+     *
+     * Opens a socket for the given number, port and IP protocol.  Before
+     * using open, you must use either resolveUrl() or setIpAddr().
+     * @ingroup API
+     * @param numSock - The number of the socket to open.
+     * @param port - the IP port to open
+     * @param tcp - set true for TCP, false for UDP
+     * @param timeoutSec - the amount of time in seconds to wait for the open to complete
+     * @return true if success, else false.
+     */
+    bool openSocket(uint16_t numSock, uint16_t port, bool tcp, uint16_t timeOutSec = 30);
+
+    /**
+     *
+     * Opens a socket for the given text URL, number, port and IP protocol.
+     * @ingroup API
+     * @param numSock - The number of the socket to open.
+     * @param url - a c-string text URL, the one to open a socket for.
+     * @param port - the IP port to open.
+     * @param tcp - set true for TCP, false for UDP.
+     * @param timeoutSec - the amount of time in seconds to wait for the open to complete.
+     * @return true if success, else false.
+     */
+    bool openSocketUrl(uint16_t numSock, const char * url, uint16_t port, bool tcp, uint16_t timeOutSec = 30);
+
+    /**
+     *
+     * Opens a socket for the given text IP address, number, port and IP protocol.
+     * @ingroup API
+     * @param numSock - The number of the socket to open.
+     * @param ipAddr - a c-string text IP address like: "192.168.0.1".
+     * @param port - the IP port to open.
+     * @param tcp - set true for TCP, false for UDP.
+     * @param timeoutSec - the amount of time in seconds to wait for the open to complete.
+     * @return true if success, else false.
+     */
+    bool openSocketIpAddr(uint16_t numSock, const char * ipAddr, uint16_t port, bool tcp, uint16_t timeOutSec = 30);
+
+
+    /**
+     *
+     * Write data bytes to a Socket, the Socket must already be open.
+     * @ingroup API
+     * @param numSock - The number of the socket to open.
+     * @parma s - an array of bytes to write to the socket.
+     * @param n - the number of bytes to write.
+     * @return true if success, else false.
+     */
+     bool write(uint16_t numSock, const uint8_t * s, uint32_t n);
+
+    /**
+     *
+     * Poll to read available data bytes from an already open Socket.  This method
+     * will retry reads to what setReadRetries() sets it to and the delay in between
+     * retries that is set with setReadRetryWait()
+     * @ingroup API
+     * @param numSock - The number of the socket to open.
+     * @parma readBuf - a pointer to where read will put the data.
+     * @param maxReadBufLen - The number of bytes readBuf has room for.
+     * @return the number of bytes actually read into readBuf.  0 is a valid value if no data is available.
+     */
+    size_t read(uint16_t numSock, uint8_t * readBuf, uint32_t maxReadBufLen);
+    
+    /**
+     *
+     * Poll to read available data bytes from an already open Socket.  This method
+     * will retry reads to what setReadRetries() sets it to and the delay in between
+     * retries that is set with setReadRetryWait()
+     * @ingroup API
+     * @param numSock - The number of the socket to open.
+     * @parma readBuf - a pointer to pointer that will be set to point to an internal byte buffer that contains any read data.
+     * @return the number of bytes actually read into the pointer that readBuf points to.  0 is a valid value if no data is available.
+     */
+    size_t read(uint16_t numSock, const uint8_t ** readBuf);
+
+    /**
+     *
+     * Set the number of retries that the read methods will use.  If a read returns 0 data this setting will have the read
+     * re-read to see if new data is available.
+     * @ingroup API
+     * @param numSock - The number of the socket to open.
+     * @parma retries - the number of retries to perform.
+     * @return none.
+     */
+    void setReadRetries(uint16_t numSock, uint16_t retries);
+
+    /**
+     *
+     * Set the time between retires that the read methods will use.  If a read returns 0 data this setting will have the read
+     * re-read and use this amount of delay in between the re-reads.
+     * @ingroup API
+     * @param numSock - The number of the socket to open.
+     * @parma waitMs - the amount of time in mS to wait between retries.
+     * @return none.
+     */
+    void setReadRetryWait(uint16_t numSock, uint16_t waitMs);
+
+    /**
+     *
+     * Closes an already open Socket.
+     * @ingroup API
+     * @param numSock - The number of the socket to open.
+     * @return true if success else false.
+     */
+    bool closeSocket(uint16_t numSock);
+
+    /**
+     *
+     * Sets the amount of time to wait between the raw AT commands that are sent to the WNC modem.
+     * Generally you don't want to use this but it is here just in case.
+     * @ingroup API
+     * @param toMs - num mS to wait between the AT cmds.
+     * @return none.
+     */
+    void setWncCmdTimeout(uint16_t toMs);
+
+    /**
+     *
+     * Gets the IP address of the given socket number.
+     * @ingroup API
+     * @param numSock - The number of the socket to open.
+     * @param myIpAddr - a c-string that contains the socket's IP address.
+     * @return true if success else false.
+     */
+    bool getIpAddr(uint16_t numSock, char myIpAddr[MAX_LEN_IP_STR]);
+    
+    /**
+     *
+     * Enables debug output from this class.
+     * @ingroup API
+     * @param on - true enables debug output, false disables
+     * @param moreDebugOn - true enables verbose debug, false truncates debug output.
+     * @return none.
+     */
+    void enableDebug(bool on, bool moreDebugOn);
+    
+    ///////////////////////////////////////////
+    //  SMS messaging
+    ///////////////////////////////////////////
+
+    static const uint16_t MAX_WNC_SMS_MSG_SLOTS = 3;   // How many SMS messages the WNC can store and receive at a time.
+    static const uint16_t MAX_WNC_SMS_LENGTH    = 160; // The maximum length of a 7-bit SMS message the WNC can send and receive.
+    
+    /** Struct for SMS messages */
+    struct WncSmsInfo
+    {
+        // Content
+        char   idx;
+        string number;
+        string date;
+        string time;
+        string msg;
+        
+        // Attributes
+        bool incoming;
+        bool unsent;
+        bool unread;
+        bool pduMode;
+        bool msgReceipt;
+    };
+
+    /** Struct to contain a list of SMS message structs */
+    struct WncSmsList
+    {
+        uint8_t msgCount;
+        WncSmsInfo e[MAX_WNC_SMS_MSG_SLOTS];
+    };
+
+    /**
+     *
+     * Sends an SMS text message to someone.
+     * @ingroup API
+     * @param phoneNum - c-string 15 digit MSISDN number or ATT Jasper number (standard phone number not supported because ATT IoT SMS does not support it).
+     * @param text - the c-string text to send to someone.
+     * @return true if success else false.
+     */
+    bool sendSMSText(const char * const phoneNum, const char * const text);
+
+    /**
+     *
+     * Incoming messages are stored in a log in the WNC modem, this will read that
+     * log.
+     * @ingroup API
+     * @param log - the log contents if reading it was successful.
+     * @return true if success else false.
+     */
+    bool readSMSLog(struct WncSmsList * log);
+
+    /**
+     *
+     * Incoming messages are stored in a log in the WNC modem, this will read out
+     * messages that are unread and also then mark them read.
+     * @ingroup API
+     * @param w - a list of SMS messages that unread messages will be put into.
+     * @param deleteRead - if a message is read and this is set true the message will be deleted from the WNC modem log.
+     * If it is false the message will remain in the internal log but be marked as read.
+     * @return true if success else false.
+     */
+    bool readUnreadSMSText(struct WncSmsList * w, bool deleteRead = true);
+    
+    /**
+     *
+     * Saves a text message into internal SIM card memory of the WNC modem.
+     * There are only 3 slots available this is for unread, read and saved.
+     * @ingroup API
+     * @param phoneNum - c-string 15 digit MSISDN number or ATT Jasper number (standard phone number not supported because ATT IoT SMS does not support it).
+     * @param text - the c-string text to send to someone.
+     * @param msgIdx - the slot position to save the message: '1', '2', '3'
+     * @return true if success else false.
+     */
+    bool saveSMSText(const char * const phoneNum, const char * const text, char * msgIdx);
+
+    /**
+     *
+     * Sends a prior stored a text message from internal SIM card memory of the WNC modem.
+     * If no messages are stored the behaviour of this method is undefined.
+     * @ingroup API
+     * @param msgIdx - the slot position to save the message: '1', '2', '3'
+     * @return true if success else false.
+     */
+    bool sendSMSTextFromMem(char msgIdx);
+
+    /**
+     *
+     * Deletes a prior stored a text message from internal SIM card memory of the WNC modem.
+     * If no messages are stored the behaviour of this method is undefined.
+     * @ingroup API
+     * @param msgIdx - the slot position to save the message: '1', '2', '3' or '*' deletes them all.
+     * @return true if success else false.
+     */
+    bool deleteSMSTextFromMem(char msgIdx);
+    
+    /**
+     *
+     * Retreives the SIM card ICCID number.
+     * @ingroup API
+     * @param iccid - a pointer to C++ string that contains the retrieved number.
+     * @return true if success else false.
+     */
+    bool getICCID(string * iccid);
+
+    /**
+     *
+     * Converts an ICCID number into a MSISDN number.  The ATT SMS system for IoT only allows use of the 15-digit MSISDN number.
+     * @ingroup API
+     * @param iccid - the number to convert.
+     * @param msisdn - points to a C++ string that has the converted number.
+     * @return true if success else false.
+     */
+    bool convertICCIDtoMSISDN(const string & iccid, string * msisdn);
+    
+    ///////////////////////////////////////////
+    // Neighborhood Cell Info
+    ///////////////////////////////////////////
+    
+    /**
+     *
+     * Fetches the signal quality log from the WNC modem.
+     * @ingroup API
+     * @param log - a pointer to an internal buffer who's contents contain the signal quality metrics.
+     * @return The number of chars in the log.
+     */
+    size_t getSignalQuality(const char ** log);
+    
+    /**  A struct for the WNC modem Date and Time */
+    struct WncDateTime
+    {
+        uint8_t  year;
+        uint8_t  month;
+        uint8_t  day;
+        uint8_t  hour;
+        uint8_t  min;
+        uint8_t  sec;
+    };
+
+    /**
+     *
+     * Fetches the cell tower's time and date.  The time is accurate when read
+     * but significant delays exist between the time it is read and returned.
+     * @ingroup API
+     * @param tod - User supplies a pointer to a tod struct and this method fills it in.
+     * @return true if success else false.
+     */
+    bool getTimeDate(struct WncDateTime * tod);
+    
+    /**
+     *
+     * ICMP Pings a URL, the results are only output to the debug log for now!
+     * @ingroup API
+     * @param url - a c-string whose URL is to be pinged.
+     * @return true if success else false.
+     */
+    bool pingUrl(const char * url);
+
+    /**
+     *
+     * ICMP Pings an IP, the results are only output to the debug log for now!
+     * @ingroup API
+     * @param ip - a c-string whose IP is to be pinged.
+     * @return true if success else false.
+     */
+    bool pingIp(const char * ip);
+    
+    /**
+     *
+     * Allows a user to send a raw AT command to the WNC modem.
+     * @ingroup API
+     * @param cmd - the c-string cmd to send like: "AT"
+     * @param resp - a pointer to the c-string cmd's response.
+     * @param sizeRespBuf - how large the command response buffer is, sets the max response length.
+     * @param ms_timeout - how long to wait for the WNC to respond to your command.
+     * @return the number of characters in the response from the WNC modem.
+     */
+    size_t sendCustomCmd(const char * cmd, char * resp, size_t sizeRespBuf, int ms_timeout);
+
+protected:
+
+    // Debug output methods
+    int dbgPutsNoTime(const char * s, bool crlf = true);
+    int dbgPuts(const char * s, bool crlf = true);
+    const char * _to_string(int64_t value);
+    const char * _to_hex_string(uint8_t value);    
+
+    // Sends commands to WNC via
+    enum AtCmdErr_e {
+        WNC_AT_CMD_OK,
+        WNC_AT_CMD_ERR,
+        WNC_AT_CMD_ERREXT,
+        WNC_AT_CMD_ERRCME,
+        WNC_AT_CMD_INVALID_RESPONSE,
+        WNC_AT_CMD_TIMEOUT,
+        WNC_AT_CMD_NO_CELL_LINK,
+        WNC_AT_CMD_WNC_NOT_ON
+    };
+
+    bool waitForPowerOnModemToRespond(uint8_t powerUpTimeoutSecs);    
+    AtCmdErr_e sendWncCmd(const char * const s, string ** r, int ms_timeout);
+
+    // Users must define these functionalities in the inheriting class:
+        // General I/O and timing:
+    virtual int putc(char c)              = 0;
+    virtual int puts(const char * s)      = 0;
+    virtual char getc(void)               = 0;
+    virtual int charReady(void)           = 0;
+    virtual int dbgWriteChar(char b)      = 0;
+    virtual int dbgWriteChars(const char *b) = 0;
+    virtual void waitMs(int t)            = 0;
+    virtual void waitUs(int t)            = 0;
+    virtual bool initWncModem(uint8_t powerUpTimeoutSecs) = 0;
+    
+        // Isolate OS timers
+    virtual int  getLogTimerTicks(void)  = 0;
+    virtual void startTimerA(void)       = 0;
+    virtual void stopTimerA(void)        = 0;
+    virtual int  getTimerTicksA_mS(void) = 0;
+    virtual void startTimerB(void)       = 0;
+    virtual void stopTimerB(void)        = 0;
+    virtual int  getTimerTicksB_mS(void) = 0;
+    
+private:
+
+    bool softwareInitMdm(void);
+    bool checkCellLink(void);
+    AtCmdErr_e mdmSendAtCmdRsp(const char * cmd, int timeout_ms, string * rsp, bool crLf = true);
+    size_t mdmGetline(string * buff, int timeout_ms);
+    bool at_at_wnc(void);
+    bool at_init_wnc(bool hardReset = false);
+    int16_t at_sockopen_wnc(const char * const ip, uint16_t port, uint16_t numSock, bool tcp, uint16_t timeOutSec);
+    bool at_sockclose_wnc(uint16_t numSock);
+    bool at_dnsresolve_wnc(const char * s, string * ipStr);
+    AtCmdErr_e at_sockwrite_wnc(const uint8_t * s, uint16_t n, uint16_t numSock, bool isTcp);
+    AtCmdErr_e at_sockread_wnc(uint8_t * pS, uint16_t * numRead, uint16_t n, uint16_t numSock, bool isTcp);
+    AtCmdErr_e at_sockread_wnc(string * pS, uint16_t numSock, bool isTcp);
+    bool at_reinitialize_mdm(void);
+    AtCmdErr_e at_send_wnc_cmd(const char * s, string ** r, int ms_timeout);
+    bool at_setapn_wnc(const char * const apnStr);
+    bool at_sendSMStext_wnc(const char * const phoneNum, const char * const text);
+    bool at_get_wnc_net_stats(WncIpStats * s);
+    bool at_readSMSlog_wnc(string ** log);
+    size_t at_readSMStext_wnc(const char ** log);
+    size_t at_readSMStext_wnc(const char n, const char ** log);
+    bool at_getrssiber_wnc(int16_t * dBm, int16_t * ber3g);
+    void closeOpenSocket(uint16_t numSock);
+    bool sockWrite(const uint8_t * const s, uint16_t n, uint16_t numSock, bool isTcp);
+    bool at_sendSMStextMem_wnc(char n);
+    bool at_deleteSMSTextFromMem_wnc(char n);
+    bool at_saveSMStext_wnc(const char * const phoneNum, const char * const text, char * msgIdx);
+    size_t at_getSignalQuality_wnc(const char ** log);
+    bool at_gettimedate_wnc(struct WncDateTime * tod);
+    bool at_ping_wnc(const char * ip);
+    bool at_geticcid_wnc(string * iccid);
+    
+    // Utility methods
+    void sendCmd(const char * cmd, bool crLf);
+    void sendCmd(const char * cmd, unsigned n, unsigned wait_uS, bool crLf);    
+    inline void rx_char_wait(void) {
+        // waitUs(1000);
+    }
+    
+    // Important constants
+    static const uint16_t MAX_WNC_READ_BYTES        = 1500;                            // This bounds the largest amount of data that the WNC read from a socket will return
+    static const uint16_t MAX_WNC_WRITE_BYTES       = MAX_WNC_READ_BYTES;              // This is the largest amount of data that the WNC can write per sockwrite.
+    static const uint16_t MAX_LEN_WNC_CMD_RESPONSE  = (MAX_WNC_READ_BYTES * 2 + 100);  // Max number of text characters in a WNC AT response *2 because bytes are converted into 2 hex-digits +100 for other AT@ chars.
+    static const uint16_t WNC_AUTO_POLL_MS          = 250;   // Sets default (may be overriden with method) poll interval (currently not used, future possible feature.
+    static const uint16_t WNC_CMD_TIMEOUT_MS        = 40000; // Sets default (may be overriden) time that the software waits for an AT response from the WNC.
+    static const uint16_t WNC_QUICK_CMD_TIMEOUT_MS  = 2000;  // Used for simple commands that should immediately respond such as "AT", cmds that are quicker than WNC_CMD_TIMEOUT_MS.
+    static const uint16_t WNC_WAIT_FOR_AT_CMD_MS    = 0;     // Wait this much between multiple in a row AT commands to the WNC.
+    static const uint16_t WNC_SOFT_INIT_RETRY_COUNT = 10;    // How many times the WNC will be tried to revive if it stops responding.
+    static const uint16_t WNC_DNS_RESOLVE_WAIT_MS   = 60000; // How much time to wait for the WNC to respond to a DNS resolve/lookup.
+    static const uint16_t WNC_TRUNC_DEBUG_LENGTH    = 80;    // Always make this an even number, how many chars for the debug output before shortening the debug ouput, this is used when moreDebug = false. 
+    static const uint16_t WNC_APNSET_TIMEOUT_MS     = 60000; // How long to wait for the WNC to respond to setting the APN string.
+    static const uint16_t WNC_PING_CMD_TIMEOUT_MS   = 60000; // Amount of time to wait for the WNC to respond to AT@PINGREQ (with cmd default params for timeout, does not change WNC cmd's timeout) 
+    static const int      WNC_REINIT_MAX_TIME_MS    = 60000; // How long to wait for the WNC to reset after it was already up and running after power-up.
+    static const uint16_t WNC_SOCK_CLOSE_RETRY_CNT  = 3;     // How many times to try to close the socket if the WNC gives an error.
+    static const char * const INVALID_IP_STR;                // Just a string set to an IP address when DNS resolve fails.
+        
+    struct WncSocketInfo_s {
+        int16_t numWncSock;
+        bool open;
+        string myIpAddressStr;
+        uint16_t myPort;
+        uint8_t readRetries;
+        uint16_t readRetryWaitMs;
+        bool isTcp;
+        uint16_t timeOutSec;
+    };
+
+    static WncSocketInfo_s m_sSock[MAX_NUM_WNC_SOCKETS];
+    static const WncSocketInfo_s defaultSockStruct;
+    static WncState_e m_sState;
+    static uint16_t m_sCmdTimeoutMs;
+    static string m_sApnStr;
+    static string m_sWncStr;
+    static uint8_t m_sPowerUpTimeoutSecs;
+    static bool m_sDebugEnabled;
+    static bool m_sMoreDebugEnabled;
+    static bool m_sCheckNetStatus;
+    static bool m_sReadyForSMS;
+};
+
+};  // End namespace WncController_fk
+
+#endif
+