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:
fkellermavnet
Date:
Wed Aug 31 02:06:26 2016 +0000
Child:
1:ac2de545b981
Commit message:
Create a library for the WNC controller.

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
WncControllerK64F.cpp Show annotated file Show diff for this revision Revisions of this file
WncControllerK64F.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WncController.cpp	Wed Aug 31 02:06:26 2016 +0000
@@ -0,0 +1,1708 @@
+/*
+    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
+*/
+
+
+#include <cstdlib>
+#include <cctype>
+#include "WncController.h"
+
+namespace WncController_fk {
+
+/////////////////////////////////////////////////////
+// Static initializers
+/////////////////////////////////////////////////////
+WncController::WncSocketInfo_s WncController::m_sSock[MAX_NUM_WNC_SOCKETS] = {
+    { false, "192.168.0.1", 80, 0, 25, true, 30 } // ,
+//    { 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);
+}
+
+
+/**
+ *  \brief Constructor for UART controlled WNC
+ *
+ *  \param [in] wnc_uart - Reference to a SerialBuffered object which will
+ *  be used as the bus to control the WNC.
+ *
+ *  \return None.
+ *
+ *  \details Adding another way to talk to the WNC, like I2C or USB,
+ *  a constructor should be added for each type just like the SerialBuffered
+ *  constructor below.
+ */
+WncController::WncController(const char * const apnStr)
+{
+    m_sApnStr = apnStr;
+}
+
+void WncController::enableDebug(bool on, bool moreDebugOn)
+{
+    m_sDebugEnabled = on;
+    m_sMoreDebugEnabled = moreDebugOn;
+}
+
+/**
+ *  \brief Used internally but also make public for a user of the Class to interrogate state as well.
+ *
+ *  \param [in] None.
+ *
+ *  \return The state of the WNC Modem.
+ *
+ *  \details None.
+ */
+WncController::WncState_e WncController::getWncStatus(void)
+{
+    return (m_sState);
+}
+
+/**
+ *  \brief Return signal quality dBm level
+ *
+ *  \param [in] None.
+ *
+ *  \return The dBm signal level at the time of the request.
+ *
+ *  \details This polls (at the time of the call) the cell signal.
+ */
+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);
+}
+
+
+/**
+ *  \brief  Power up and down (down not implemented yet)
+ *
+ *  \param [in] on - set true to power on, otherwise false
+ *
+ *  \return None.
+ *
+ *  \details Power-on works but not power-down.  This will manipulate WNC Shield hardware
+ *  and bring it to life.  It will also initialize the WNC enough to get it to be able to open sockets
+ *  (with AT commands)
+ */
+bool WncController::powerWncOn(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(m_sApnStr.c_str());
+        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_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);
+        if (respStr->size() < sizeRespBuf) {
+            strcpy(resp, respStr->c_str());
+            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);
+}
+
+
+/**
+ *  \brief Look-up a URL text string and convert into an IP Address string.
+ *
+ *  \param [in] url - the URL to lookup.  numSock - the socket number to resolve.
+ *
+ *  \return true - if the IP address has been resolved. false - if the URL could not be resolved.
+ *
+ *  \details None.
+ */
+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);
+}
+
+/**
+ *  \brief Set IP Address string
+ *
+ *  \param [in] numSock - socket reference to set the string for. ipStr - text string of the IP
+ *  address you want to talk to.  There is no sanity check - beware!!!
+ *
+ *  \return true - if the IP address has been set. false - if the IP could not be set.
+ *
+ *  \details None.
+ */
+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);
+    }
+}
+
+/**
+ *  \brief Opens a WNC socket.
+ *
+ *  \param [in] sockNum - the number of the socket to open.  ipAddr - a string containing
+ *  the IP address.  port - the IP port number to open the socket connection.
+ *
+ *  \return true - if the socket is/was opened.  false otherwise.
+ *
+ *  \details None.
+ */
+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!");
+            at_sockclose_wnc(numSock);
+            m_sSock[numSock].open = false;
+        }
+        
+        m_sSock[numSock].myPort = port;
+        m_sSock[numSock].isTcp = tcp;
+        m_sSock[numSock].timeOutSec = timeOutSec;
+        m_sSock[numSock].open = at_sockopen_wnc(m_sSock[numSock].myIpAddressStr, port, numSock, tcp, timeOutSec);
+        if (m_sSock[numSock].open == false) {
+            dbgPuts("Socket open fail!!!!");
+            // 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(numSock);
+        }
+    }
+    else {
+        dbgPuts("Bad socket num or IP!");
+        return (false);
+    }
+
+    return (m_sSock[numSock].open);
+}
+
+/**
+ *  \brief Write bytes of data to an open socket
+ *
+ *  \param [in] sockNum - the number of the socket to write.  s - a string containing
+ *  the byte data to send must be less than = 1500.
+ *
+ *  \return true - if the write was successful.  false otherwise.
+ *
+ *  \details The results of the write do not have anything to do with the data
+ *  arriving at the endpoint.
+ */
+ 
+bool WncController::sockWrite(const char * s, uint32_t n, uint16_t numSock, bool isTcp)
+{
+    bool result = true;;
+    
+    AtCmdErr_e cmdRes = at_sockwrite_wnc(s, n, numSock, isTcp);
+    if (cmdRes != WNC_AT_CMD_OK) {
+        if ((cmdRes == WNC_AT_CMD_ERREXT) || (cmdRes == WNC_AT_CMD_TIMEOUT) || (cmdRes == WNC_AT_CMD_ERRCME))
+        {
+            // This may throw away any data that hasn't been wrote 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 char * 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);
+}
+
+/**
+ *  \brief Poll and read back data from the WNC (if it has any)
+ *  If auto poll is enabled this read might fail (return with no data).
+ *
+ *  \param [in] sockNum - the number of the socket to read.  result - a string pointer containing
+ *  the byte data readback from the WNC.
+ *
+ *  \return The number of bytes/chars that are read from the socket.
+ *
+ *  \details DO NOT use the same string as is passed to the auto poll setup method!
+ */
+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;
+            size_t numRead;
+            do {
+                AtCmdErr_e cmdRes;
+                if (maxReadBufLen < MAX_WNC_READ_BYTES) {
+                    cmdRes = at_sockread_wnc(readBuf, &numRead, maxReadBufLen, numSock, m_sSock[numSock].isTcp);
+                    dbgPutsNoTime("Warning: read back buffer to small?");
+                }
+                else
+                    cmdRes = at_sockread_wnc(readBuf, &numRead, MAX_WNC_READ_BYTES, numSock, 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 (cmdRes == WNC_AT_CMD_ERREXT || cmdRes == WNC_AT_CMD_TIMEOUT)
+                    {
+                        // 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!");
+    }
+
+    return (numCopied);
+}
+
+/**
+ *  \brief Set how many times the above read method will retry if data is not returned.
+ *
+ *  \param [in] sockNum - the number of the socket to set.  retries - how many times to
+ *  poll until data is found.
+ *
+ *  \return None.
+ *
+ *  \details None.
+ */
+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!");
+}
+
+/**
+ *  \brief Set how long between retries to wait.
+ *
+ *  \param [in] sockNum - the number of the socket to set.  readRetryWaitMs - how long to wait
+ *  before doing the read poll (calling read(...)).
+ *
+ *  \return None.
+ *
+ *  \details None.
+ */
+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!");
+}
+
+/**
+ *  \brief Close the socket.
+ *
+ *  \param [in] sockNum - the number of the socket to close.
+ *
+ *  \return None.
+ *
+ *  \details None.
+ */
+bool WncController::closeSocket(uint16_t numSock)
+{
+    if (numSock < MAX_NUM_WNC_SOCKETS) {
+
+        if (false == at_sockclose_wnc(numSock))
+            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);
+}
+
+// Note: If you want to make it more portable, create a
+// arecharsavailable() and readchar()
+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 - 1)) && (getUsTimerTicksB() < timeout_ms)) {
+        if (byteReady()) {
+            chin_last = chin;
+            chin = (char)getc();
+            if (isprint(chin)) {
+                buff += (char)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;
+            }
+        }
+        rx_char_wait();
+    }
+    stopTimerB();
+    
+    if (len >= (MAX_LEN_WNC_CMD_RESPONSE - 1))
+        dbgPuts("Max cmd length reply exceeded!");
+
+    return (len);
+}
+
+// Eventually this should try to reinstate the sockets open
+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);
+  }
+}
+
+
+// Sets a global with failure or success, assumes 1 thread all the time
+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) {
+        // 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 {
+        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");
+        at_sockclose_wnc(numSock);
+        m_sSock[numSock].open = at_sockopen_wnc(m_sSock[numSock].myIpAddressStr, m_sSock[numSock].myPort, numSock, m_sSock[numSock].isTcp, m_sSock[numSock].timeOutSec);
+        if (m_sSock[numSock].open == false)
+            dbgPuts("Failed to re-open socket!");
+    } while (m_sSock[numSock].open == false);
+}
+
+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);
+    }
+}
+
+size_t WncController::readSMSLog(const char ** log)
+{
+    size_t n;
+
+    n = at_readSMSlog_wnc(log);
+    if (n == 0)
+        dbgPuts("readSMSLog: Failed!");
+        
+    return (n);
+}
+
+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);
+}
+
+size_t WncController::readSMSText(const char ** log)
+{
+    size_t n;
+
+    n = at_readSMStext_wnc(log);
+    if (n == 0)
+        dbgPuts("readSMSText: Failed!");
+        
+    return (n);
+}
+
+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;
+            unsigned i;
+            size_t pe;
+            size_t ps = pRespStr->rfind("\"");
+            if (ps != string::npos) {
+                ps += 2;  // Skip the , after the "
+                pe = ps;
+                for(i=0;i<4;i++)
+                    pe = pRespStr->find(".", 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;
+                for(i=0;i<3;i++)
+                    pe = pRespStr->find(".", 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;
+                for(i=0;i<3;i++)
+                    pe = pRespStr->find(".", 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;
+                for(i=0;i<3;i++)
+                    pe = pRespStr->find(".", 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;
+                for(i=0;i<3;i++)
+                    pe = pRespStr->find(".", 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); dbgPuts(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); dbgPuts(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;
+    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);
+            at_send_wnc_cmd("AT+CPMS=\"SM\",\"SM\",\"SM\"", &pRespStr, m_sCmdTimeoutMs);
+            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);
+}
+
+
+size_t WncController::at_readSMSlog_wnc(const char ** log)
+{
+    string * pRespStr;
+
+    if (at_send_wnc_cmd("AT+CMGL", &pRespStr, m_sCmdTimeoutMs) == WNC_AT_CMD_OK)
+        *log = pRespStr->c_str();
+    else
+        *log = "\0";
+        
+    return (pRespStr->size());
+}
+
+size_t WncController::at_readSMStext_wnc(const char ** log)
+{
+    string * pRespStr;
+
+    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);
+
+  // 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
+  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);
+}
+
+
+bool WncController::at_sockopen_wnc(const string & ipStr, 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)
+    {
+        cmd_str = "AT@SOCKCONN=";
+        cmd_str += _to_string(numSock + 1);
+        cmd_str += ",\"";
+        cmd_str += ipStr;
+        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 (res == WNC_AT_CMD_OK);
+    }
+    else
+        return (false);
+}
+
+bool WncController::at_sockclose_wnc(uint16_t numSock)
+{
+    string * pRespStr;
+    string cmd_str("AT@SOCKCLOSE=");
+
+    cmd_str += _to_string(numSock + 1);
+    // Don't check the cell status to close the socket
+    return (WNC_AT_CMD_OK == at_send_wnc_cmd(cmd_str.c_str(), &pRespStr, m_sCmdTimeoutMs));
+}
+
+bool WncController::at_dnsresolve_wnc(const char * s, string * ipStr)
+{
+    string * pRespStr;
+    string str(s);
+
+    ipStr->erase(); // Clear out string until resolved!
+    str = "AT@DNSRESVDON=\"" + str;
+    str += "\"";
+    if (sendWncCmd(str.c_str(), &pRespStr, WNC_DNS_RESOLVE_WAIT_MS) == WNC_AT_CMD_OK) {
+        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 {
+            dbgPuts("\rWaiting ", false); dbgPutsNoTime(_to_string(timeoutSecs), false);
+            timeoutSecs--;
+            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);    
+    }
+    
+    return (false);
+}
+
+WncController::AtCmdErr_e WncController::at_sockwrite_wnc(const char * s, uint32_t n, uint16_t numSock, bool isTcp)
+{
+    AtCmdErr_e result;
+
+    if ((n > 0) && (n <= MAX_WNC_WRITE_BYTES)) {
+        string * pRespStr;
+        char num2str[10];
+        string cmd_str;
+
+        if (isTcp == true)
+            cmd_str="AT@SOCKWRITE=";
+        else
+            cmd_str="AT@SOCKWRITE="; // "AT@SOCKSEND=";
+
+        cmd_str += _to_string(numSock + 1);
+        cmd_str += ",";
+        _to_string(n);
+        cmd_str += num2str;
+        cmd_str += ",\"";
+        while(*s != '\0') {
+            _to_string((int8_t)*s++);
+            // Always 2-digit ascii hex:
+            if (strlen(num2str) == 1) {
+                num2str[2] = '\0';
+                num2str[1] = num2str[0];
+                num2str[0] = '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(uint8_t * pS, uint32_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 + 1);
+        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) {
+            pos_start = pRespStr->find("\"")  + 1;
+            pos_end   = pRespStr->rfind("\"") - 1;
+        
+            // Make sure search finds what it's looking for!
+            if (pos_start != string::npos && pos_end != string::npos)
+                i = (pos_end - pos_start + 1);  // Num hex chars, 2 per byte
+            else
+                i = 0;
+            
+            // If data convert the hex string into byte values
+            if (i > 0 && i <= (2*MAX_WNC_READ_BYTES))
+            {
+                string byte;
+                while (pos_start < pos_end) {
+                    byte = pRespStr->substr(pos_start, 2);
+                    *pS += (char)strtol(byte.c_str(), NULL, 16);
+                    pos_start += 2;
+                    (*numRead)++;
+                }
+            }
+        }
+    }
+    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);
+//        sendCmdSlow(cmd, n, 500);  // 3rd arg is micro seconds between chars sent
+    }
+
+    startTimerA();
+    while (getUsTimerTicksA() < timeout_ms) {
+        n = mdmGetline(*rsp, timeout_ms - getUsTimerTicksA());
+
+        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
+    cmdRes2 = at_send_wnc_cmd("AT+CPIN?", &pRespStr, m_sCmdTimeoutMs);     // Check if SIM locked
+    
+    if ((cmdRes1 != WNC_AT_CMD_OK) && (cmdRes2 != WNC_AT_CMD_OK))
+    {
+        if (m_sMoreDebugEnabled)
+        {
+            if ((cmdRes1 == WNC_AT_CMD_TIMEOUT) || (cmdRes2 == WNC_AT_CMD_TIMEOUT))
+                dbgPuts("------------ WNC No Response! --------->");
+            else
+                dbgPuts("------------ WNC Cmd Error! ----------->");
+        }
+        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)
+    {
+        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 ---------------->");
+    }
+    
+    m_sState = WNC_ON;
+    
+    return (true);
+}
+
+int WncController::dbgPutsNoTime(const char * s, bool crlf)
+{
+    if (m_sDebugEnabled == true) {
+        int r = dbgWriteBytes(s);
+        if (crlf == true)
+            return (dbgWriteBytes("\r\n"));
+        else
+            return (r);
+    }
+    else
+        return 0;
+};
+
+int WncController::dbgPuts(const char * s, bool crlf)
+{
+    dbgPutsNoTime("[*] ", false);
+    dbgPutsNoTime(_to_string(getLogTimerTicks()), false);
+    dbgPutsNoTime(" ");
+
+    int r = dbgPutsNoTime(s, false);
+    if (crlf == true)
+        return (dbgPutsNoTime("\r\n", false));
+    else
+        return (r);
+};
+    
+void WncController::sendCmd(const char * cmd, bool crLf)
+{
+    puts(cmd);
+    if (crLf == true)
+        puts("\r\n");
+}
+
+// WNC used to have troubles handling full speed, seems to not need this now.
+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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WncController.h	Wed Aug 31 02:06:26 2016 +0000
@@ -0,0 +1,412 @@
+/*
+    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
+*/
+
+#ifndef __WNCCONTROLLER_H_
+#define __WNCCONTROLLER_H_
+
+#include <string>
+#include <stdint.h>
+
+namespace WncController_fk {
+    
+using namespace std;
+
+/**
+ *  \file WncController.h
+ *  \brief This mbed C++ class is for controlling the WNC
+ *  Cellular modem via the AT command interface.  This was
+ *  developed with respect to version 1.3 of the WNC authored
+ *  spec.  This class is only designed to have 1 instantiation
+ *  it is also not multi-thread safe.
+ */
+
+
+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
+{
+    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];
+};
+
+struct WncDateTime
+{
+    uint8_t  year;
+    uint8_t  month;
+    uint8_t  day;
+    uint8_t  hour;
+    uint8_t  min;
+    uint8_t  sec;
+};
+        
+class WncController
+{
+public:
+
+    static const unsigned MAX_NUM_WNC_SOCKETS = 1;  // 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
+    };
+
+    /**
+     *  \brief Constructor for UART controlled WNC
+     *
+     *  \param [in] wnc_uart - Reference to a SerialBuffered object which will
+     *  be used as the bus to control the WNC.  apnStr = a text string for
+     *  the cellular APN name.
+     *
+     *  \return None.
+     *
+     *  \details Adding another way to talk to the WNC, like I2C or USB,
+     *  a constructor should be added for each type just like the SerialBuffered
+     *  constructor below.  Assumes UART is enabled, setup and ready to go. This
+     *  class will read and write to this UART.
+     */
+    WncController(const char * const apnStr);
+    
+    // WncController( const char * const apnStr, MODSERIAL * wnc_uart, MODSERIAL * debug_uart = NULL);
+    
+    /**
+     *  \brief Used internally but also make public for a user of the Class to interrogate state as well.
+     *
+     *  \param [in] None.
+     *
+     *  \return The state of the WNC Modem.
+     *
+     *  \details None.
+     */
+    WncState_e getWncStatus(void);
+    
+    bool setApnName(const char * const apnStr);
+
+    /**
+     *  \brief Return signal quality dBm level
+     *
+     *  \param [in] None.
+     *
+     *  \return The dBm signal level at the time of the request.
+     *
+     *  \details This polls (at the time of the call) the cell signal.
+     */
+    int16_t getDbmRssi(void);
+    int16_t get3gBer(void);
+
+    /**
+     *  \brief  Power up and down (down not implemented yet)
+     *
+     *  \param [in] NXP Pins that are critical for the initialization of the WNC Shield.
+     *
+     *  \return true if request successful else false.
+     *
+     *  \details Power-on works but not power-down.  This will manipulate WNC Shield hardware
+     *  and bring it to life.  It will also initialize the WNC enough to get it to be able to open sockets
+     *  (with AT commands)
+     */
+    bool powerWncOn(uint8_t powerUpTimeoutSecs = MAX_POWERUP_TIMEOUT);
+
+    /**
+     *  \brief  Query the WNC modem for its Internet attributes
+     *
+     *  \param [in] Pointer to a struct where to put the info.
+     *
+     *  \return true if request successful else false.
+     *
+     *  \details This method will do a few sanity checks and then gather the
+     *  fields of the struct.
+     */
+    bool getWncNetworkingStats(WncIpStats * s);
+
+    /**
+     *  \brief Look-up a URL text string and convert into an IP Address string.
+     *
+     *  \param [in] url - the URL to lookup.  numSock - the socket reference.
+     *
+     *  \return true - if the IP address has been resolved. false - if the URL could not be resolved.
+     *
+     *  \details None.
+     */
+    bool resolveUrl(uint16_t numSock, const char * url);
+
+    /**
+     *  \brief Set IP Address string
+     *
+     *  \param [in] numSock - socket reference to set the string for. ipStr - text string of the IP
+     *  address you want to talk to.  There is no sanity check - beware!!!
+     *
+     *  \return true - if the IP address has been set. false - if the IP could not be set.
+     *
+     *  \details None.
+     */
+    bool setIpAddr(uint16_t numSock, const char * ipStr);
+
+    /**
+     *  \brief Opens a WNC socket.
+     *
+     *  \param [in] sockNum - the number of the socket to open.  ipAddr - a string containing
+     *  the IP address.  port - the IP port number to open the socket connection.
+     *
+     *  \return true - if the socket is/was opened.  false otherwise.
+     *
+     *  \details None.
+     */
+    bool openSocket(uint16_t numSock, uint16_t port, bool tcp, uint16_t timeOutSec = 30);
+
+    /**
+     *  \brief Write bytes of data to an open socket
+     *
+     *  \param [in] sockNum - the number of the socket to write.  s - a string containing
+     *  the byte data to send.
+     *
+     *  \return true - if the write was successful.  false otherwise.
+     *
+     *  \details The results of the write do not have anything to do with the data
+     *  arriving at the endpoint.
+     */
+     bool write(uint16_t numSock, const char * s, uint32_t n);
+
+    /**
+     *  \brief Poll and read back data from the WNC (if it has any)
+     *  If auto poll is enabled this read might fail (return with no data).
+     *
+     *  \param [in] sockNum - the number of the socket to read.  result - a string pointer containing
+     *  the byte data readback from the WNC.
+     *
+     *  \return The number of bytes/chars that are read from the socket.
+     *
+     *  \details DO NOT use the same string as is passed to the auto poll setup method!
+     */
+    size_t read(uint16_t numSock, uint8_t * readBuf, uint32_t maxReadBufLen);
+
+    /**
+     *  \brief Set how many times the above read method will retry if data is not returned.
+     *
+     *  \param [in] sockNum - the number of the socket to set.  retries - how many times to
+     *  poll until data is found.
+     *
+     *  \return None.
+     *
+     *  \details None.
+     */
+    void setReadRetries(uint16_t numSock, uint16_t retries);
+
+    /**
+     *  \brief Set how long between retries to wait.
+     *
+     *  \param [in] sockNum - the number of the socket to set.  waitMs - how long to wait
+     *  before doing the read poll (calling read(...)).
+     *
+     *  \return None.
+     *
+     *  \details None.
+     */
+    void setReadRetryWait(uint16_t numSock, uint16_t waitMs);
+
+    /**
+     *  \brief Close the socket.
+     *
+     *  \param [in] sockNum - the number of the socket to close.
+     *
+     *  \return None.
+     *
+     *  \details None.
+     */
+    bool closeSocket(uint16_t numSock);
+
+    void setWncCmdTimeout(uint16_t toMs) {
+        m_sCmdTimeoutMs = toMs;
+    };
+    
+    bool getIpAddr(uint16_t numSock, char myIpAddr[MAX_LEN_IP_STR]);
+    
+    void enableDebug(bool on, bool moreDebugOn);
+    
+    ///////////////////////////////////////////
+    //  SMS messaging
+    ///////////////////////////////////////////
+
+    bool sendSMSText(const char * const phoneNum, const char * const text);
+
+    size_t readSMSLog(const char ** log);
+    
+    bool saveSMSText(const char * const phoneNum, const char * const text, char * msgIdx);
+    
+    bool sendSMSTextFromMem(char msgIdx);
+
+    bool deleteSMSTextFromMem(char msgIdx);
+    
+    size_t readSMSText(const char ** log);
+    
+    ///////////////////////////////////////////
+    // Neighborhood Cell Info
+    ///////////////////////////////////////////
+    size_t getSignalQuality(const char ** log);
+    
+    //  Date Time
+    bool getTimeDate(struct WncDateTime * tod);
+    
+    // Ping
+    bool pingUrl(const char * url);
+    bool pingIp(const char * ip);
+    
+    // User command:
+    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);
+
+    // 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
+    };
+
+    // Users must define these functionalities:
+    virtual int putc(char c)              = 0;
+    virtual int puts(const char * s)      = 0;
+    virtual char getc(void)               = 0;
+    virtual int byteReady(void)           = 0;
+    virtual int dbgWriteByte(char b)      = 0;
+    virtual int dbgWriteBytes(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  getUsTimerTicksA(void) = 0;
+    virtual void startTimerB(void)      = 0;
+    virtual void stopTimerB(void)       = 0;
+    virtual int  getUsTimerTicksB(void) = 0;
+        
+    bool waitForPowerOnModemToRespond(uint8_t powerUpTimeoutSecs);    
+
+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);
+    AtCmdErr_e sendWncCmd(const char * const s, string ** r, int ms_timeout);
+    bool at_at_wnc(void);
+    bool at_init_wnc(bool hardReset = false);
+    bool at_sockopen_wnc(const string & ipStr, 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 char * s, uint32_t n, uint16_t numSock, bool isTcp);
+    AtCmdErr_e at_sockread_wnc(uint8_t * pS, uint32_t * numRead, uint16_t n, 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);
+    size_t at_readSMSlog_wnc(const char ** log);
+    size_t at_readSMStext_wnc(const char ** log);
+    bool at_getrssiber_wnc(int16_t * dBm, int16_t * ber3g);
+    void closeOpenSocket(uint16_t numSock);
+    bool sockWrite(const char * s, uint32_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);
+
+    // 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, careful here large chunks come out of the heap
+    static const uint16_t MAX_WNC_WRITE_BYTES       = MAX_WNC_READ_BYTES;
+    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
+    static const uint16_t WNC_AUTO_POLL_MS          = 250; // Sets default (may be overriden with method) poll interval
+    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"
+    static const uint16_t WNC_WAIT_FOR_AT_CMD_MS    = 0; // 40; // Wait this much between AT commands, this is what WNC advises (they said 20mS, I added margin).
+    static const uint16_t MAX_WNC_SMS_LENGTH        = 150;
+    static const uint16_t WNC_SOFT_INIT_RETRY_COUNT = 10;
+    static const uint16_t WNC_DNS_RESOLVE_WAIT_MS   = 60000;
+    static const uint16_t WNC_TRUNC_DEBUG_LENGTH    = 80;  // Always make this an even number
+    static const uint16_t WNC_APNSET_TIMEOUT_MS     = 60000;
+    static const uint16_t WNC_PING_CMD_TIMEOUT_MS   = 60000;  //  Amount of time to wait for AT@PINGREQ with default params to timeout 
+    static const int      WNC_REINIT_MAX_TIME_MS    = 60000;
+    static const char * const INVALID_IP_STR;
+        
+    struct WncSocketInfo_s {
+        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 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WncControllerK64F.cpp	Wed Aug 31 02:06:26 2016 +0000
@@ -0,0 +1,160 @@
+/*
+    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 Cellular Modem
+    @version       1.0
+    @date          July 2016
+    @author        Fred Kellerman
+*/
+
+#include "WncControllerK64F.h"
+
+using namespace WncControllerK64F_fk;
+
+WncControllerK64F::WncControllerK64F(const char * const apnStr, struct WncGpioPinListK64F * pPins, Serial * wnc_uart, Serial * debug_uart)
+: WncController(apnStr)
+{
+    m_logTimer.start(); // Start the log timer now!    
+    m_pDbgUart = debug_uart;
+    m_pWncUart = wnc_uart;
+    m_gpioPinList = *pPins;
+}
+
+int WncControllerK64F::putc(char c)
+{
+    return (m_pWncUart->putc(c));
+}
+
+int WncControllerK64F::puts(const char * s)
+{
+    return (m_pWncUart->puts(s));
+}
+
+char WncControllerK64F::getc(void)
+{
+    return (m_pWncUart->getc());
+}
+
+int WncControllerK64F::byteReady(void)
+{
+    return (m_pWncUart->readable());
+}
+
+int WncControllerK64F::dbgWriteByte(char b)
+{
+    if (m_pDbgUart != NULL)
+        return (m_pDbgUart->putc(b));
+    else
+        return (0);
+}
+
+int WncControllerK64F::dbgWriteBytes(const char * b)
+{
+    if (m_pDbgUart != NULL)
+        return (m_pDbgUart->puts(b));
+    else
+        return (0);
+}
+
+bool WncControllerK64F::initWncModem(uint8_t powerUpTimeoutSecs)
+{
+    // Hard reset the modem (doesn't go through
+    // the signal level translator)
+    *m_gpioPinList.mdm_reset = 0;
+
+    // disable signal level translator (necessary
+    // for the modem to boot properly).  All signals
+    // except mdm_reset go through the level translator
+    // and have internal pull-up/down in the module. While
+    // the level translator is disabled, these pins will
+    // be in the correct state.
+    *m_gpioPinList.shield_3v3_1v8_sig_trans_ena = 0;
+
+    // While the level translator is disabled and ouptut pins
+    // are tristated, make sure the inputs are in the same state
+    // as the WNC Module pins so that when the level translator is
+    // enabled, there are no differences.
+    *m_gpioPinList.mdm_uart2_rx_boot_mode_sel = 1;   // UART2_RX should be high
+    *m_gpioPinList.mdm_power_on = 0;                 // powr_on should be low
+    *m_gpioPinList.mdm_wakeup_in = 1;                // wake-up should be high
+    *m_gpioPinList.mdm_uart1_cts = 0;                // indicate that it is ok to send
+
+    // Now, wait for the WNC Module to perform its initial boot correctly
+    waitMs(1000);
+
+    // The WNC module initializes comms at 115200 8N1 so set it up
+    m_pWncUart->baud(115200);
+
+    //Now, enable the level translator, the input pins should now be the
+    //same as how the M14A module is driving them with internal pull ups/downs.
+    //When enabled, there will be no changes in these 4 pins...
+    *m_gpioPinList.shield_3v3_1v8_sig_trans_ena = 1;
+
+    return (waitForPowerOnModemToRespond(powerUpTimeoutSecs));
+}
+
+void WncControllerK64F::waitMs(int t)
+{
+    wait_ms(t);
+}
+
+void WncControllerK64F::waitUs(int t)
+{
+    wait_ms(t);
+}
+
+int  WncControllerK64F::getLogTimerTicks(void)
+{
+    return (m_logTimer.read_us());
+}
+
+void WncControllerK64F::startTimerA(void)
+{
+    m_timerA.start();
+    m_timerA.reset();
+}
+
+void WncControllerK64F::stopTimerA(void)
+{
+    m_timerA.stop();
+}
+
+int  WncControllerK64F::getUsTimerTicksA(void)
+{
+    return (m_timerA.read_us());
+}
+
+void WncControllerK64F::startTimerB(void)
+{
+    m_timerB.start();
+    m_timerB.reset();
+}
+
+void WncControllerK64F::stopTimerB(void)
+{
+    m_timerB.stop();
+}
+
+int  WncControllerK64F::getUsTimerTicksB(void)
+{
+    return (m_timerB.read_us());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WncControllerK64F.h	Wed Aug 31 02:06:26 2016 +0000
@@ -0,0 +1,108 @@
+/*
+    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
+*/
+
+#ifndef __WNCCONTROLLERK64F_H_
+#define __WNCCONTROLLERK64F_H_
+
+#include <string>
+#include <stdint.h>
+#include "mbed.h"
+#include "MODSERIAL.h"
+#include "WncController.h"
+
+namespace WncControllerK64F_fk {
+
+using namespace WncController_fk;
+using namespace std;
+
+struct WncGpioPinListK64F {
+    /////////////////////////////////////////////////////
+    // NXP GPIO Pins that are used to initialize the WNC Shield
+    /////////////////////////////////////////////////////
+    DigitalOut * mdm_uart2_rx_boot_mode_sel;   // on powerup, 0 = boot mode, 1 = normal boot
+    DigitalOut * mdm_power_on;                 // 0 = turn modem on, 1 = turn modem off (should be held high for >5 seconds to cycle modem)
+    DigitalOut * mdm_wakeup_in;                // 0 = let modem sleep, 1 = keep modem awake -- Note: pulled high on shield
+    DigitalOut * mdm_reset;                    // active high
+    DigitalOut * shield_3v3_1v8_sig_trans_ena; // 0 = disabled (all signals high impedence, 1 = translation active
+    DigitalOut * mdm_uart1_cts;
+};    
+
+class WncControllerK64F : public WncController
+{
+public:
+    /**
+     *  \brief Constructor for UART controlled WNC
+     *
+     *  \param [in] wnc_uart - Reference to a SerialBuffered object which will
+     *  be used as the bus to control the WNC.  apnStr = a text string for
+     *  the cellular APN name.
+     *
+     *  \return None.
+     *
+     *  \details Adding another way to talk to the WNC, like I2C or USB,
+     *  a constructor should be added for each type just like the SerialBuffered
+     *  constructor below.  Assumes UART is enabled, setup and ready to go. This
+     *  class will read and write to this UART.
+     */
+    WncControllerK64F( const char * const apnStr, struct WncGpioPinListK64F * pPins, Serial * wnc_uart, Serial * debug_uart = NULL);
+    
+private:
+
+    // Disallow copy
+//    WncControllerK64F operator=(WncControllerK64F lhs);
+
+    // Users must define these functionalities:
+    virtual int putc(char c);
+    virtual int puts(const char * s);
+    virtual char getc(void);
+    virtual int byteReady(void);
+    virtual int dbgWriteByte(char b);
+    virtual int dbgWriteBytes(const char *b);
+    virtual bool initWncModem(uint8_t powerUpTimeoutSecs);
+    virtual void waitMs(int t);
+    virtual void waitUs(int t);
+    
+    virtual int  getLogTimerTicks(void);
+    virtual void startTimerA(void);
+    virtual void stopTimerA(void);
+    virtual int  getUsTimerTicksA(void);
+    virtual void startTimerB(void);
+    virtual void stopTimerB(void);
+    virtual int  getUsTimerTicksB(void);
+
+    Serial * m_pDbgUart;
+    Serial * m_pWncUart;
+    WncGpioPinListK64F m_gpioPinList;
+    Timer m_logTimer;
+    Timer m_timerA;
+    Timer m_timerB;
+};
+
+};  // End namespace WncController_fk
+
+#endif
\ No newline at end of file