Cellular library for MTS Socket Modem Arduino Shield devices from Multi-Tech Systems

Dependents:   mtsas mtsas mtsas mtsas

Files at this revision

API Documentation at this revision

Comitter:
Vanger
Date:
Mon Jul 14 21:11:50 2014 +0000
Parent:
25:817e9d94bfd7
Parent:
30:1326b623919a
Child:
32:7d5581159bed
Commit message:
Pulled MTS-Cellular changes into MTS-Cellular-dev branch.; Also implemented setSocketCloseable(); Modified read(),write(), and sendEscapeCommand() to account for socketCloseable boolean flag.

Changed in this revision

Cellular/Cellular.h Show annotated file Show diff for this revision Revisions of this file
Cellular/EasyIP.cpp Show annotated file Show diff for this revision Revisions of this file
Cellular/EasyIP.h Show annotated file Show diff for this revision Revisions of this file
Cellular/UIP.h Show annotated file Show diff for this revision Revisions of this file
--- a/Cellular/Cellular.cpp	Wed Jul 09 14:44:48 2014 +0000
+++ b/Cellular/Cellular.cpp	Mon Jul 14 21:11:50 2014 +0000
@@ -132,21 +132,8 @@
     return UNKNOWN;
 }
 
-Code Cellular::setApn(const std::string& apn)
-{
-    if (type == MTSMC_H5_IP || type == MTSMC_H5 || type == MTSMC_G3) {
-        Code code = sendBasicCommand("AT#APNSERV=\"" + apn + "\"", 1000);
-        if (code != MTS_SUCCESS) {
-            return code;
-        }
-        this->apn = apn;
-        return code;
-    } else {
-        logInfo("CDMA radios don't need an APN");
-        return MTS_SUCCESS;
-    }
-}
-
+//Removed setAPN to be implemented in the individual cellular classes,
+//as UIP and EasyIP implement it in different ways.
 
 Code Cellular::setDns(const std::string& primary, const std::string& secondary)
 {
--- a/Cellular/Cellular.h	Wed Jul 09 14:44:48 2014 +0000
+++ b/Cellular/Cellular.h	Mon Jul 14 21:11:50 2014 +0000
@@ -197,8 +197,8 @@
     */
     virtual bool init(MTSBufferedIO* io);
     
-    /**
-    *
+    /** Sets up the physical connection pins
+    *   (DTR,DCD, and RESET obviously)
     */
     bool configureSignals(unsigned int DCD = NC, unsigned int DTR = NC, unsigned int RESET = NC);
 
@@ -234,7 +234,7 @@
     * @param the APN as a string.
     * @returns the standard AT Code enumeration.
     */
-    virtual Code setApn(const std::string& apn);
+    virtual Code setApn(const std::string& apn)=0;
 
     /** This method is used to set the DNS which enables the use of URLs instead
     * of IP addresses when making a socket connection.
@@ -322,6 +322,13 @@
     * @returns the enumeration name as a string.
     */
     static std::string getRadioNames(Radio radio);
+    /** A method for changing the echo commands from radio.
+    * @param state Echo mode is off (an argument of 1 turns echos off, anything else turns echo on)
+    * @returns standard Code enumeration
+    */
+    virtual Code echo(bool state)=0; //Implemented the same way in both UIP and EasyIP, 
+                                        //and thus could be moved to cellular class
+    virtual Code setSocketCloseable(bool enabled)=0;
 
 protected:
     MTSBufferedIO* io; //IO interface obect that the radio is accessed through.
--- a/Cellular/CellularFactory.cpp	Wed Jul 09 14:44:48 2014 +0000
+++ b/Cellular/CellularFactory.cpp	Mon Jul 14 21:11:50 2014 +0000
@@ -11,7 +11,7 @@
     std::string reply;
     Cellular::Radio type;
     Cellular* cell;
-
+    
     /* wait for radio to get into a good state */
     while (true) {
         if (sendCommand(io, "AT", 1000).find("OK") != string::npos) {
@@ -20,14 +20,13 @@
         } else {
             logTrace("waiting on radio...");
         }
-
         wait(1);
     }
-
+    
     /* "ATI4" gets us the model (HE910, DE910, etc) */
     for (int i = 0; i < 5; i++) {
         model = sendCommand(io, "ATI4", 2000);
-        if (model.find("error") == string::npos && model.find("ERROR") == string::npos) {
+        if (model.find("error") == string::npos && model.find("ERROR") == string::npos && !model.empty()) {
             /* didn't get an error - keep going */
             break;
         }
@@ -35,14 +34,15 @@
         wait(1);
     }
 
-    /* AT#VVERSION is a IUP specific AT command
+    /* AT#VVERSION is a UIP specific AT command
      * if we get an error response, we're not using a UIP board */
     reply = sendCommand(io, "AT#VVERSION", 2000);
-    if (reply.find("error") != string::npos) {
+    if ((reply.find("ERROR") != string::npos) || (reply.find("error") != string::npos)) {
         uip = false;
     } else {
         uip = true;
     }
+    
 
     if (uip && model.find("HE910") != string::npos) {
         type = Cellular::MTSMC_H5_IP;
@@ -56,7 +56,7 @@
         type = Cellular::MTSMC_C2_IP;
         logDebug("radio model: CE910");
         cell = new UIP(type);
-    /*
+    
     } else if (model.find("HE910") != string::npos) {
         type = Cellular::MTSMC_H5;
         logDebug("radio model: HE910");
@@ -73,7 +73,7 @@
         type = Cellular::MTSMC_C2;
         logDebug("radio model: CE910");
         cell = new EasyIP(type);
-    */
+    
     } else {
         logError("cannot continue - could not determine radio type");
         return NULL;
--- a/Cellular/CellularFactory.h	Wed Jul 09 14:44:48 2014 +0000
+++ b/Cellular/CellularFactory.h	Mon Jul 14 21:11:50 2014 +0000
@@ -2,7 +2,7 @@
 #define CELLULARFACTORY_H
 
 #include "UIP.h"
-//#include "EasyIP.h"
+#include "EasyIP.h"
 #include "MTSBufferedIO.h"
 
 namespace mts {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Cellular/EasyIP.cpp	Mon Jul 14 21:11:50 2014 +0000
@@ -0,0 +1,780 @@
+// This is a template from UIP.cpp for now, will modify code and implement it as I go
+
+
+
+#include "mbed.h"
+#include "EasyIP.h"
+#include "MTSText.h"
+#include "MTSLog.h"
+#include "CellUtils.h"
+
+
+
+using namespace mts;
+
+bool EasyIP::sendEscapeCommand()
+{
+    //string Cellular::sendCommand(const std::string& command, unsigned int timeoutMillis, char esc)
+    if(io == NULL) {
+        logError("MTSBufferedIO not set");
+        return false;
+    }
+    if(!socketOpened) {
+        logError("Socket is not open. Can not send AT escape sequence (+++)");
+        return false;
+    }
+    
+    if(!socketCloseable) {
+        logError("Socket is not closeable");
+        return false;
+    }
+    
+    io->rxClear();
+    io->txClear();
+    
+    std::string result;
+    unsigned int timeoutMillis = 2000;
+    const int size_cmd = 3;
+    //Attempt to write command
+    wait(1); //Format for +++ command is 1 second wait, send +++, then another second wait
+             //1s wait after command is implemented as a polling function for 2 seconds
+             //Option: Could change wait periods to be longer/shorter (0-255)*50ms
+    if(io->write("+++", size_cmd, timeoutMillis) != size_cmd) {
+        //Failed to write command
+        logError("failed to send command to radio within %d milliseconds", timeoutMillis);
+        return false;
+    }
+    
+    int timer = 0;
+    char tmp[256];
+    tmp[255] = 0;
+    bool done = false;
+    io->read(tmp,255,0);
+    bool exitmode = false;
+    
+    do {
+        wait(0.1);
+        timer += 100;
+        //Make a non-blocking read call by passing timeout of zero
+            int size = io->read(tmp,255,0);    //1 less than allocated (timeout is instant)
+            if(size > 0) {
+                result.append(tmp, size);
+            }
+        if(result.find("OK") != std::string::npos) {
+            exitmode = true;
+            done = true;
+        } else if(result.find("NO CARRIER") != std::string::npos) {
+            exitmode = true;
+            done = true;
+        } else if(result.find("ERROR") != std::string::npos) {
+            exitmode = false;
+            done = true;
+        }
+        if(timer >= timeoutMillis) {
+            logDebug("Escape sequence [+++] timed out after %d milliseconds", timeoutMillis);
+            exitmode = true;
+            done = true;
+        }
+    } while (!done);
+    
+    return exitmode;
+}
+
+bool EasyIP::socketCheck() {
+    bool status = false;
+    std::string socketInfo = "9"; //9 is unrecognized
+    std::vector<std::string> params;
+    
+    if(sendEscapeCommand()) {
+        socketOpened = false;
+        if(sendBasicCommand("AT", 1000) == MTS_SUCCESS) {
+            socketInfo = sendCommand("AT#SS=1", 2000);
+            if(socketInfo.find("OK") != std::string::npos) {
+                //Found valid response
+                params = Text::split(socketInfo, "\r\n");
+                params = Text::split(params[1], ",");
+                socketInfo = params[1];
+                //Check comparison of params[1] to response codes
+            } else {
+                logError("Could not determine socket status[%s]",socketInfo.c_str());
+                socketInfo == "9"; //9 is unrecognized
+            }
+        }
+    } else {
+        status = false; //Return value of socketOpened when checking
+    }
+    //Check socket status query
+    if(socketInfo == "2" || socketInfo == "3" || socketInfo == "1" || socketInfo == "4") {
+        status = true; //2 and 3 are suspended connections
+    } else if(socketInfo == "0" || socketInfo == "5") {
+        status = false; //0 is closed socket, probably won't occur
+    } else {
+        logError("Could not determine socket status");
+        status = false; //anything else is unknown status
+    }
+    
+    if(status) {
+        std::string reconnect = sendCommand("AT#SO=1", 2000);
+        if(reconnect.find("CONNECT") != std::string::npos || reconnect.find("OK") != std::string::npos) {
+        } else {
+            logError("Failed to resume socket connection");
+        }
+    }
+    return status;
+}
+
+EasyIP::EasyIP(Radio type)
+{
+    //Not sure how the construction process is done, 
+    //but assuming it works for both EasyIP and UIP the same way.
+    this->type = type;
+    io = NULL;
+    dcd = NULL;
+    dtr = NULL;
+    resetLine = NULL;
+    echoMode = true;
+    pppConnected = false;
+    socketMode = TCP;
+    socketOpened = false;
+    socketCloseable = true;
+    local_port = 0;
+    local_address = "";
+    host_port = 0;
+}
+
+EasyIP::~EasyIP()
+{
+    //Same reasoning for the destructor as the constructor,
+    //assuming it works for UIP, it will work for EasyIP
+    if (dtr != NULL) {
+        dtr->write(1);
+    }
+    
+    delete dcd;
+    delete dtr;
+    delete resetLine;
+}
+
+//Initializes the MTS IO Buffer
+bool EasyIP::init(MTSBufferedIO* io)
+{
+    if (! Cellular::init(io)) {
+        return false;
+    }
+
+    logDebug("radio type: %s", Cellular::getRadioNames(type).c_str());
+    return true;
+}
+
+bool EasyIP::connect()
+{
+    //Check if APN is not set, if so, connect will not work.
+    if (type == MTSMC_H5_IP || type == MTSMC_H5 || type == MTSMC_G3) {
+        if(apn.size() == 0) {
+            logDebug("APN is not set");
+            return false;
+        }
+    }
+    
+    //Check if socket is open
+    //flag stored in Cellular.h
+    if(socketOpened) {
+        return true;
+    }
+
+    //Check if already connected
+    //by calling the function isConnected() in EasyIP.cpp
+    if(isConnected()) {
+        return true;
+    }
+    //Create an mbed timer object
+    Timer tmr;
+    //Check Registration: AT+CREG? == 0,1
+    //(Does the AT command inside Cellular class)
+    tmr.start();
+    do {
+        Registration registration = getRegistration();
+        if(registration != REGISTERED) {
+            logTrace("Not Registered [%d] ... waiting", (int)registration);
+            wait(1);
+        } else {
+            break;
+        }
+    } while(tmr.read() < 30); 
+    //Check RSSI: AT+CSQ
+    //Does the command inside Cellular
+    tmr.reset();
+    do {
+        int rssi = getSignalStrength();
+        logDebug("Signal strength: %d", rssi);
+        if((rssi == 99) || (rssi == -1)) {
+            logTrace("No Signal ... waiting");
+            wait(1);
+        } else {
+            break;
+        }
+    } while(tmr.read() < 30);
+
+    //Similar to AT#CONNECTIONSTART: Make a PPP connection
+    if (type == MTSMC_H5 || type == MTSMC_G3) {
+        logDebug("Making PPP Connection Attempt. APN[%s]", apn.c_str());
+    } else {
+        logDebug("Making PPP Connection Attempt");
+    }
+    //The main thing going on; Sends the AT command to start a connection
+    //Assuming context is already stored in the modem
+    std::string pppResult = sendCommand("AT#SGACT=1,1", 2000);
+    std::vector<std::string> parts;
+    if(pppResult.find("OK") != std::string::npos) {
+        parts = Text::split(pppResult, "\r\n");
+        if(parts.size() >= 2) {
+            parts = Text::split(parts[1], " ");
+            local_address = parts[1];
+        }
+        logInfo("PPP Connection Established: IP[%s]", local_address.c_str());
+        pppConnected = true;
+
+    } else {
+        pppResult = sendCommand("AT#SGACT?", 2000);
+        if(pppResult.empty() || (pppResult.find("ERROR") != std::string::npos)) {
+            pppConnected = false;
+        } else {
+            if(pppResult.find("1,1") != std::string::npos) {
+                pppConnected = true;
+            } else {
+                pppConnected = false;
+            }
+        }
+    }
+
+    return pppConnected;
+}
+
+void EasyIP::disconnect()
+{
+    //AT#SGACT=1,0: Close a PPP connection
+    logDebug("Closing PPP Connection");    
+    if(socketOpened) {
+        if(!close()) { //Calls another EasyIP function to close socket before disconnect
+            logDebug("Failed to close socket for disconnect");
+            return; //Can't close connection without AT commands
+                    //(and thus need socket closed)
+        }
+    }
+    //Sends AT#SGACT=1,0 command
+    if(sendBasicCommand("AT#SGACT=1,0", 1000) == MTS_SUCCESS) {
+        pppConnected = false;
+        logDebug("Successfully closed PPP Connection");
+    }
+    pppConnected = false; //Cell will drop connection if we go silent
+    return;
+}
+
+bool EasyIP::isConnected()
+{
+    std::string stateString;
+    std::vector<std::string> pieces;
+    //state flags for various connection checks
+    bool signal = false, regist = false, active = false;
+    
+    //1) Check if APN was set if we're on an HSPA radio
+    if (type == MTSMC_H5_IP || type == MTSMC_H5 || type == MTSMC_G3) {
+        if(apn.size() == 0) {
+            logDebug("APN is not set");
+            return false;
+        }
+    }
+    
+    //2) Check that we do not have a live connection up
+    if(socketOpened) {
+        logDebug("Socket is opened");
+        return true;
+    }
+    
+    
+    //3) Query the radio
+    //Check antenna signal
+    std::string reply = sendCommand("AT+CSQ", 500);
+    if(reply.empty() || (reply.find("ERROR") != std::string::npos)) {
+        signal = false;
+    } else {
+        pieces = Text::split(reply, "\r\n");
+        if(pieces.size() >= 2) {
+            pieces = Text::split(pieces[1], " ");
+            if(pieces.size() >= 2) {
+                if((pieces[1].find("0,0") != std::string::npos) || (pieces[1].find("99,99") != std::string::npos)) {
+                    signal = false;
+                } else {
+                    signal = true;
+                }
+            }
+        }
+    }
+    
+    //Check cell tower registration
+    reply = sendCommand("AT+CREG?", 500);
+    if(reply.empty() || (reply.find("ERROR") != std::string::npos)) {
+        regist = false;
+    } else {
+        pieces = Text::split(reply, "\r\n");
+        if(pieces.size() >= 2) {
+            pieces = Text::split(pieces[1], " ");
+            if(pieces.size() >= 2) {
+                if((pieces[1].find("0,1") != std::string::npos) || (pieces[1].find("0,5") != std::string::npos)) {
+                    regist = true; //1 for connected, 5 for roaming connected
+                } else {
+                    regist = false; //Cell tower not registered
+                }
+            }
+        }
+    }
+    
+    //Check active mode (SGACT = 1,1)
+    reply = sendCommand("AT#SGACT?", 500);
+    if(reply.empty() || (reply.find("ERROR") != std::string::npos)) {
+        active = false;
+    } else {
+        pieces = Text::split(reply, "\r\n");
+        if(pieces.size() >= 2) {
+            pieces = Text::split(pieces[1], " ");
+            if(pieces.size() >= 2) {
+                if(pieces[1].find("1,1") != std::string::npos) {
+                    active = true; //1 for an active connection mode
+                } else {
+                    active = false; //0, or unknown value, is an inactive connection mode
+                }
+            }
+        }
+    }
+    //4) Determine radio state
+    if(regist && signal) {
+        if(pppConnected) {
+            if(active) {
+                if(ping()) {
+                    stateString = "CONNECTED";
+                    pppConnected = true;
+                } else {
+                    stateString = "AUTHENTICATING";
+                    pppConnected = true;
+                    return false; //Return false instead of changing pppConnected due to the fact
+                                  //that it is connected to ppp, it just can't ping successfully
+                }
+            } else {
+                stateString = "DISCONNECTING";
+                pppConnected = false;
+            }
+        } else {
+            if(active) {
+                if(ping()) {
+                    pppConnected = true;
+                    logWarning("Internal PPP state tracking differs from radio (DISCONNECTED:CONNECTED)");
+                    stateString = "CONNECTED";
+                } else {
+                    stateString = "CONNECTING";
+                }
+            } else {
+                stateString = "IDLE";
+            }
+        }
+    } else if(regist != signal) {
+        stateString = "CHECKING";
+        pppConnected = false;
+    } else if(!regist && !signal) {
+        stateString = "DISCONNECTED";
+        pppConnected = false;
+    }
+    //Log results if necessary
+    if(stateString != "CONNECTED") {
+        logWarning("Internal PPP state tracking differs from radio (CONNECTED:%s)",stateString.c_str());
+    }
+    return pppConnected;
+}
+
+//Binds the socket to a specific port if able
+bool EasyIP::bind(unsigned int port)
+{
+    if(socketOpened) {
+        logError("socket is open. Can not set local port");
+        return false;
+    }
+    if(port > 65535) {
+        logError("port out of range (0-65535)");
+        return false;
+    }
+    local_port = port;
+    return true;
+}
+
+bool EasyIP::open(const std::string& address, unsigned int port, Mode mode)
+{
+    char sOpenSocketCmd[256] = {0}; //String for AT command
+    std::string sMode = "";
+    int typeSocket = 0;
+    int closeType = 0;
+    
+    //1) Check that we do not have a live connection up
+    if(socketOpened) {
+        //Check that the address, port, and mode match
+        if(host_address != address || host_port != port || socketMode != mode) {
+            if(socketMode == TCP) {
+                logError("TCP socket already opened [%s:%d]", host_address.c_str(), host_port);
+            } else {
+                logError("UDP socket already opened [%s:%d]", host_address.c_str(), host_port);
+            }
+            return false;
+        }
+
+        logDebug("Socket already opened");
+        return true;
+    }
+
+    //2) Check Parameters
+    if(port > 65535) {
+        logError("port out of range (0-65535)");
+        return false;
+    }
+
+    //3) Check PPP connection
+    if(!isConnected()) {
+        logError("PPP not established.  Attempting to connect");
+        if(!connect()) {
+            logError("PPP connection failed");
+            return false;
+        } else {
+            logDebug("PPP connection established");
+        }
+    }
+    //No way to "set" port except on socket call;
+    //Going to need to warn if local_port was not set.
+    if(!local_port) {
+        logDebug("No local port was set: 0");
+    }
+    
+    //4) Set escape sequence to not be transmitted
+    if(sendBasicCommand("AT#SKIPESC=1", 2000) != MTS_SUCCESS) {
+        logWarning("Failed to disable escape sequence transmission on data mode suspension");
+    }
+    
+    if(mode == TCP) {
+        typeSocket = 0;
+        sMode = "TCP";
+    } else {
+        typeSocket = 1;
+        sMode = "UDP";
+    }
+    
+    if(socketCloseable) {
+        closeType = 0;
+    } else {
+        closeType = 255;
+    }
+    //5) Open Socket  
+    sprintf(sOpenSocketCmd, "AT#SD=1,%d,%d,%s,%d,%d,0", typeSocket, port, address.c_str(),closeType , local_port);
+    std::string response = sendCommand(sOpenSocketCmd, 5000);
+    
+    if(response.find("CONNECT") != std::string::npos) {
+        host_address = address;
+        host_port = port;
+        
+        logInfo("Opened %s Socket [%s:%d]", sMode.c_str(), address.c_str(), port);
+        socketOpened = true;
+        socketMode = mode;
+    } else {
+        logWarning("Unable to open %s Socket [%s:%d]", sMode.c_str(), address.c_str(), port);
+        socketOpened = false;
+    }
+    
+    return socketOpened;
+}
+
+bool EasyIP::isOpen()
+{
+    if(io->readable()) {
+        logDebug("Assuming open, data available to read.\n\r");
+        return true;
+    }
+    return socketOpened;
+}
+
+bool EasyIP::close()
+{
+    
+    if(io == NULL) {
+        logError("MTSBufferedIO not set");
+        return false;
+    }
+
+    if(!socketOpened) {
+        logWarning("Socket close() called, but socket was not open");
+        return true;
+    }
+
+    if(!socketCloseable) {
+        logError("Socket is not closeable");
+        return false;
+    }
+    
+    if(!sendEscapeCommand()) {
+        logError("Failed to exit online mode");
+        return false;
+    } else {
+        socketOpened = false;
+    }
+    
+    if(sendBasicCommand("AT#SH=1", 2000) != MTS_SUCCESS) {
+        logDebug("Failed to close socket connection");
+    }
+    
+    Timer tmr;
+    int counter = 0;
+    char tmp[256];
+    tmr.start();
+    do {
+        if(socketOpened == false) {
+            break;
+        }
+        read(tmp, 256, 1000);
+    } while(counter++ < 10);
+
+    io->rxClear();
+    io->txClear();
+
+    return !socketOpened;
+}
+
+int EasyIP::read(char* data, int max, int timeout)
+{
+    if(io == NULL) {
+        logError("MTSBufferedIO not set");
+        return -1;
+    }
+
+    //Check that nothing is in the rx buffer
+    if(!socketOpened && !io->readable()) {
+        logError("Socket is not open");
+        return -1;
+    }
+
+    int bytesRead = 0;
+    
+
+    if(timeout >= 0) {
+        bytesRead = io->read(data, max, static_cast<unsigned int>(timeout));
+    } else {
+        bytesRead = io->read(data, max);
+    }
+    if(bytesRead > 0 && socketCloseable) {
+        //Scan for socket closed message
+        for(size_t i = 0; i < bytesRead; i++) {
+            if(data[i] == 'N') {
+                if(strstr(&data[i], "NO CARRIER")) {
+                    logTrace("Found socket closed message. Checking validity");
+                    //Close socket and Cut Off End of Message
+                    socketOpened = socketCheck(); //Verifies legitimacy of socket disconnect
+                    if(socketOpened) {
+                        logDebug("Socket still open");
+                        continue;
+                    } else {
+                        logDebug("Socket closed");
+                        data[i] = '\0';
+                        bytesRead = i;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    return bytesRead;
+}
+
+int EasyIP::write(const char* data, int length, int timeout)
+{
+    if(io == NULL) {
+        logError("MTSBufferedIO not set");
+        return -1;
+    }
+
+    if(!socketOpened) {
+        logError("Socket is not open");
+        return -1;
+    }
+
+    int bytesWritten = 0;
+    int size = length;
+    int failedWrites = 0;
+    if(timeout >= 0) {
+        Timer tmr;
+        tmr.start();
+        do {
+            int available = io->writeable();
+            if (available > 0) {
+                size = MIN(available, length - bytesWritten);
+                bytesWritten += io->write(&data[bytesWritten], size);
+            } else {
+                wait(0.05);
+            }
+        } while (tmr.read_ms() <= timeout && bytesWritten < length);
+    } else {
+        //If timeout is -1:
+        do {
+            int available = io->writeable();
+            if(available > 0) {
+                size = MIN(available, length - bytesWritten);
+                int currentWritten = io->write(&data[bytesWritten], size);
+                bytesWritten += currentWritten;
+                if(!currentWritten) {
+                    failedWrites++;
+                }
+                if(failedWrites > 10) {
+                    logError("Couldn't write any characters");
+                    return bytesWritten;
+                }
+            } else {
+                wait(0.05);
+            }
+        } while (bytesWritten < length); 
+    }
+    return bytesWritten;
+}
+
+unsigned int EasyIP::readable()
+{
+    if(io == NULL) {
+        logWarning("MTSBufferedIO not set");
+        return 0;
+    }
+    if(!socketOpened && !io->readable()) {
+        logWarning("Socket is not open");
+        return 0;
+    }
+    return io->readable();
+}
+
+unsigned int EasyIP::writeable()
+{
+    if(io == NULL) {
+        logWarning("MTSBufferedIO not set");
+        return 0;
+    }
+    if(!socketOpened) {
+        logWarning("Socket is not open");
+        return 0;
+    }
+
+    return io->writeable();
+}
+
+bool EasyIP::setDeviceIP(std::string address)
+{
+    if (address.compare("DHCP") == 0) {
+        return true;
+    } else {
+        logWarning("Radio does not support static IPs, using DHCP.\n\r");
+        return false;
+    }
+}
+
+Code EasyIP::setApn(const std::string& apn)
+{
+    if (type == MTSMC_H5 || type == MTSMC_G3) {
+         //Set IP,PPP,IPv6
+        Code code = sendBasicCommand("AT+CGDCONT=1,PPP," + apn, 1000);
+        if (code != MTS_SUCCESS) {
+            return code;
+        }
+        this->apn = apn;
+        return code; //This will return MTS_SUCCESS
+    } else {
+        logInfo("CDMA radios don't need an APN");
+        return MTS_SUCCESS;
+    }
+}
+
+void EasyIP::reset()
+{
+    disconnect();
+    if(sendBasicCommand("AT#REBOOT", 10000) != MTS_SUCCESS) {
+        logError("Socket Modem did not accept RESET command\n\r");
+    } else {
+        logWarning("Socket Modem is resetting, allow 30 seconds for it to come back\n\r");
+        return;
+    }
+}
+
+std::string EasyIP::getDeviceIP()
+{
+    return local_address;
+}
+
+//Turns off echo when it receives a 1, turns on when it receives anything else
+Code EasyIP::echo(bool state)
+{
+    Code code;
+    if (state) {
+        code = sendBasicCommand("ATE0", 1000);
+        echoMode = (code == MTS_SUCCESS) ? false : echoMode;
+    } else {
+        code = sendBasicCommand("ATE1", 1000);
+        echoMode = (code == MTS_SUCCESS) ? true : echoMode;
+    }
+    return code;
+}
+
+bool EasyIP::ping(const std::string& address)
+{
+    char buffer[256] = {0};
+    std::vector<std::string> parts;
+    int pingsRec=0;
+    int TTL=0;
+    int Timeout=0;
+    
+    //Format parameters for sending to radio
+    sprintf(buffer, "AT#PING=%s,1,32,%d", address.c_str(), (PINGDELAY*10));
+    
+    for(int pngs=0; pngs<PINGNUM; pngs++) {
+        std::string response = sendCommand(buffer, (PINGDELAY*1010)); //Send 1 ping
+        if(response.empty()) {
+            continue; //Skip current loop if send command fails
+        }
+        if(response.find("ERROR") != std::string::npos) {
+            continue; //Skip current loop if send command fails
+        }
+        parts = Text::split(response, "\r\n");
+        if(parts.size() < 2) {
+            continue;
+        }
+        parts = Text::split(parts[1], ",");
+        if(parts.size() < 4) {
+            continue;
+        }
+        //Parse TTL and Timeout values
+        Timeout = std::atoi(parts[2].c_str());
+        TTL = std::atoi(parts[3].c_str());
+                
+        if((Timeout < 600) && (TTL < 255)) {
+            pingsRec++;
+        }
+    }   //Success if less than 50% packet loss
+        if( ((pingsRec/PINGNUM)>= 0.5) ) {
+            return true;
+        }
+    return false;
+}
+
+//Pass 1 to enable socket closeable
+//Pass 0 to disable socket closeable
+Code EasyIP::setSocketCloseable(bool enabled)
+{
+    if(socketCloseable == enabled) {
+        return MTS_SUCCESS;
+    }
+
+    if(socketOpened) {
+        logError("socket is already opened. Can not set closeable");
+        return MTS_ERROR;
+    }
+
+    socketCloseable = enabled;
+
+    return MTS_SUCCESS;
+}
--- a/Cellular/EasyIP.h	Wed Jul 09 14:44:48 2014 +0000
+++ b/Cellular/EasyIP.h	Mon Jul 14 21:11:50 2014 +0000
@@ -1,4 +1,105 @@
 #ifndef SMC_H
 #define SMC_H
 
+#include <string>
+#include <vector>
+
+#include "MTSBufferedIO.h"
+#include "Cellular.h"
+
+namespace mts
+{
+/** This is a class for communicating without a Multi-Tech Systems SocketModem iCell. Instead, 
+* it uses the hayes command set to implement the same commands and functions as the UIP class.
+* (See the UIP class and documentation, "UIP.h")
+* This class supports three main types of cellular radio interactions including:
+* configuration and status AT command processing, SMS processing, and TCP Socket
+* data connections. It should be noted that the radio can not process commands or
+* SMS messages while having an open data connection at the same time. The concurrent
+* capability may be added in a future release. This class also inherits from IPStack
+* providing a common set of commands for communication devices that have an onboard
+* IP Stack. It is also integrated with the standard mbed Sockets package and can therefore
+* be used seamlessly with clients and services built on top of this interface already within
+* the mbed library.
+* The default baud rate for the cellular radio is 115200 bps.
+*/
+class EasyIP : public Cellular     //Inherits from Cellular.
+{
+private:
+    /*Function that sends +++ to radio to exit data mode
+      returns true if successful exit from online mode, else false
+    */
+    virtual bool sendEscapeCommand(); 
+    /*Switches to command mode, queries socket connection status,
+      then returns true if there is an active socket connection, 
+      else it returns false.*/
+    virtual bool socketCheck();
+public:
+/** This static function is used to create or get a reference to a
+    * Cellular object. Cellular uses the singleton pattern, which means
+    * that you can only have one existing at a time. The first time you
+    * call getInstance this method creates a new uninitialized Cellular
+    * object and returns it. All future calls to this method will return
+    * a reference to the instance created during the first call. Note that
+    * you must call init on the returned instance before mnaking any other
+    * calls. If using this class's bindings to any of the Socket package
+    * classes like TCPSocketConnection, you must call this method and the
+    * init method on the returned object first, before even creating the
+    * other objects.
+    *
+    * @returns a reference to the single Cellular obect that has been created.
+    */
+    EasyIP(Radio type);
+    /** Destructs a Cellular object and frees all related resources.
+    */
+    ~EasyIP();
+
+    virtual bool init(MTSBufferedIO* io);
+
+    // Wifi connection based commands derived from CommInterface.h
+    virtual bool connect();
+    virtual void disconnect();
+    virtual bool isConnected();
+    virtual void reset();
+
+    // TCP and UDP Socket related commands
+    // For behavior of the following methods refer to IPStack.h documentation
+    virtual bool bind(unsigned int port);
+    virtual bool open(const std::string& address, unsigned int port, Mode mode);
+    virtual bool isOpen();
+    virtual bool close();
+    virtual int read(char* data, int max, int timeout = -1);    
+    virtual int write(const char* data, int length, int timeout = -1);
+    virtual unsigned int readable();
+    virtual unsigned int writeable();
+    virtual bool ping(const std::string& address = "8.8.8.8"); //Google DNS server
+    virtual std::string getDeviceIP();
+    virtual bool setDeviceIP(std::string address = "DHCP");//Runs DHCP to configure the IP
+    
+    //Sets the APN, also sets mode to IP, might need to change
+    virtual Code setApn(const std::string& apn);
+    /** A method for configuring command ehco capability on the radio. This command
+    * sets whether sent characters are echoed back from the radio, in which case you
+    * will receive back every command you send.
+    *
+    * @param state if true echo will be turned off, otherwise it will be turned on.
+    * @returns the standard AT Code enumeration.
+    */
+    virtual Code echo(bool state);
+
+    /** This method can be used to trade socket functionality for performance.
+    * In order to enable a socket connection to be closed by the client side programtically,
+    * this class must process all read and write data on the socket to guard the special
+    * escape character used to close an open socket connection. It is recommened that you
+    * use the default of true unless the overhead of these operations is too significant.
+    *
+    * @param enabled set to true if you want the socket closeable, otherwise false. The default
+    * is true.
+    * @returns the standard AT Code enumeration.
+    */
+    virtual Code setSocketCloseable(bool enabled = true);  //ETX closes socket (ETX and DLE in payload are escaped with DLE)
+};
+
+}
+
 #endif
--- a/Cellular/UIP.cpp	Wed Jul 09 14:44:48 2014 +0000
+++ b/Cellular/UIP.cpp	Mon Jul 14 21:11:50 2014 +0000
@@ -90,9 +90,10 @@
         logDebug("Making PPP Connection Attempt");
     }
     std::string pppResult = sendCommand("AT#CONNECTIONSTART", 120000);
-    std::vector<std::string> parts = Text::split(pppResult, "\r\n");
+    
 
     if(pppResult.find("Ok_Info_GprsActivation") != std::string::npos) {
+        std::vector<std::string> parts = Text::split(pppResult, "\r\n");
         if(parts.size() >= 2) {
             local_address = parts[1];
         }
@@ -165,6 +166,7 @@
     return pppConnected;
 }
 
+
 bool UIP::bind(unsigned int port)
 {
     if(socketOpened) {
@@ -528,6 +530,21 @@
     }
 }
 
+Code UIP::setApn(const std::string& apn)
+{
+    if (type == MTSMC_H5_IP) {
+        Code code = sendBasicCommand("AT#APNSERV=\"" + apn + "\"", 1000);
+        if (code != MTS_SUCCESS) {
+            return code;
+        }
+        this->apn = apn;
+        return code; //This will return MTS_SUCCESS
+    } else {
+        logInfo("CDMA radios don't need an APN");
+        return MTS_SUCCESS;
+    }
+}
+
 void UIP::reset()
 {
     disconnect();
--- a/Cellular/UIP.h	Wed Jul 09 14:44:48 2014 +0000
+++ b/Cellular/UIP.h	Mon Jul 14 21:11:50 2014 +0000
@@ -75,6 +75,9 @@
     virtual bool ping(const std::string& address = "8.8.8.8");
     virtual std::string getDeviceIP();
     virtual bool setDeviceIP(std::string address = "DHCP");
+    
+    /** A method for setting the APN */
+    virtual Code setApn(const std::string& apn);
 
     /** A method for configuring command ehco capability on the radio. This command
     * sets whether sent characters are echoed back from the radio, in which case you
@@ -83,7 +86,7 @@
     * @param state if true echo will be turned off, otherwise it will be turned on.
     * @returns the standard AT Code enumeration.
     */
-    Code echo(bool state);
+    virtual Code echo(bool state);
 
     /** This method can be used to trade socket functionality for performance.
     * In order to enable a socket connection to be closed by the client side programtically,
@@ -95,7 +98,7 @@
     * is true.
     * @returns the standard AT Code enumeration.
     */
-    Code setSocketCloseable(bool enabled = true);  //ETX closes socket (ETX and DLE in payload are escaped with DLE)
+    virtual Code setSocketCloseable(bool enabled = true);  //ETX closes socket (ETX and DLE in payload are escaped with DLE)
 };
 
 }
--- a/Utils/CellUtils.h	Wed Jul 09 14:44:48 2014 +0000
+++ b/Utils/CellUtils.h	Mon Jul 14 21:11:50 2014 +0000
@@ -84,7 +84,7 @@
         if(size > 0) {
             result.append(tmp, size);
         }
-        done =  (result.size() == previous);
+        done =  (result.size() == previous && previous > 0);
         if(timer >= timeoutMillis) {
             if (command != "AT" && command != "at") {
                 logWarning("sendCommand [%s] timed out after %d milliseconds", command.c_str(), timeoutMillis);