This class adds HTTP, FTP and CellLocate client support for u-blox modules for the C027 and C030 boards (excepting the C030 N2xx flavour) from mbed 5.5 onwards. The HTTP, FTP and CellLocate operations are all hosted on the module, minimizing RAM consumption in the mbed MCU. It also sub-classes ublox-cellular-driver-gen to bring in SMS, USSD and modem file system support if you need to use these functions at the same time as the cellular interface.

Dependencies:   ublox-at-cellular-interface

Dependents:   example-ublox-at-cellular-interface-ext HelloMQTT ublox_new_driver_test example-ublox-at-cellular-interface-ext ... more

Files at this revision

API Documentation at this revision

Comitter:
RobMeades
Date:
Mon Jun 05 12:58:04 2017 +0000
Child:
1:26a67ab07275
Commit message:
Initial revision.

Changed in this revision

TESTS/unit_tests/cell-locate/main.cpp Show annotated file Show diff for this revision Revisions of this file
TESTS/unit_tests/cell-locate/template_mbed_app.txt Show annotated file Show diff for this revision Revisions of this file
TESTS/unit_tests/ftp/main.cpp Show annotated file Show diff for this revision Revisions of this file
TESTS/unit_tests/ftp/template_mbed_app.txt Show annotated file Show diff for this revision Revisions of this file
TESTS/unit_tests/http/main.cpp Show annotated file Show diff for this revision Revisions of this file
UbloxATCellularInterfaceExt.cpp Show annotated file Show diff for this revision Revisions of this file
UbloxATCellularInterfaceExt.h Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TESTS/unit_tests/cell-locate/main.cpp	Mon Jun 05 12:58:04 2017 +0000
@@ -0,0 +1,250 @@
+#include "mbed.h"
+#include "greentea-client/test_env.h"
+#include "unity.h"
+#include "utest.h"
+#include "UbloxATCellularInterfaceExt.h"
+#include "UDPSocket.h"
+#include "FEATURE_COMMON_PAL/nanostack-libservice/mbed-client-libservice/common_functions.h"
+#include "mbed_trace.h"
+#define TRACE_GROUP "TEST"
+
+using namespace utest::v1;
+
+// ----------------------------------------------------------------
+// COMPILE-TIME MACROS
+// ----------------------------------------------------------------
+
+// These macros can be overridden with an mbed_app.json file and
+// contents of the following form:
+//
+//{
+//    "config": {
+//        "apn": {
+//            "value": "\"my_apn\""
+//        },
+//        "run-tcp-server-test": {
+//            "value": 1
+//        },
+//        "mga-token": {
+//            "value": "\"my_token\""
+//        }
+//}
+
+// The credentials of the SIM in the board.
+#ifndef MBED_CONF_APP_DEFAULT_PIN
+// Note: this is the PIN for the SIM with ICCID
+// 8944501104169548380.
+# define MBED_CONF_APP_DEFAULT_PIN "5134"
+#endif
+
+// Network credentials.
+#ifndef MBED_CONF_APP_APN
+# define MBED_CONF_APP_APN         NULL
+#endif
+#ifndef MBED_CONF_APP_USERNAME
+# define MBED_CONF_APP_USERNAME    NULL
+#endif
+#ifndef MBED_CONF_APP_PASSWORD
+# define MBED_CONF_APP_PASSWORD    NULL
+#endif
+
+#ifndef MBED_CONF_APP_RUN_CELL_LOCATE_TCP_SERVER_TEST
+#  define MBED_CONF_APP_RUN_CELL_LOCATE_TCP_SERVER_TEST 0
+#endif
+
+// The authentication token for TCP access to the MGA server.
+#if MBED_CONF_APP_RUN_CELL_LOCATE_TCP_SERVER_TEST
+#  ifndef MBED_CONF_APP_CELL_LOCATE_MGA_TOKEN
+#    error "You must have a token for MGA server access to run Cell Locate with a TCP server"
+#  endif
+#endif
+
+// The type of response requested
+#ifndef MBED_CONF_APP_RESP_TYPE
+#  define MBED_CONF_APP_RESP_TYPE 1 // CELL_DETAILED
+#endif
+
+// The maximum number of hypotheses requested
+#ifndef MBED_CONF_APP_CELL_LOCATE_MAX_NUM_HYPOTHESIS
+#  if MBED_CONF_APP_RESP_TYPE == 2 // CELL_MULTIHYP
+#    define MBED_CONF_APP_CELL_LOCATE_MAX_NUM_HYPOTHESIS 16
+#  else
+#    define MBED_CONF_APP_CELL_LOCATE_MAX_NUM_HYPOTHESIS 1
+#  endif
+#endif
+
+#ifndef MBED_CONF_APP_CELL_LOCATE_MGA_TOKEN
+#  define MBED_CONF_APP_CELL_LOCATE_MGA_TOKEN "0"
+#endif
+
+// ----------------------------------------------------------------
+// PRIVATE VARIABLES
+// ----------------------------------------------------------------
+
+// Lock for debug prints
+static Mutex mtx;
+
+// An instance of the cellular interface
+static UbloxATCellularInterfaceExt *pDriver =
+       new UbloxATCellularInterfaceExt(MDMTXD, MDMRXD,
+                                       MBED_CONF_UBLOX_CELL_BAUD_RATE,
+                                       true);
+
+// ----------------------------------------------------------------
+// PRIVATE FUNCTIONS
+// ----------------------------------------------------------------
+
+// Locks for debug prints
+static void lock()
+{
+    mtx.lock();
+}
+
+static void unlock()
+{
+    mtx.unlock();
+}
+
+static void printCellLocateData(UbloxATCellularInterfaceExt::CellLocData *pData)
+{
+    char timeString[25];
+
+    tr_debug("Cell Locate data:");
+    if (strftime(timeString, sizeof(timeString), "%a %b %d %H:%M:%S %Y", (const tm *) &(pData->time)) > 0) {
+        tr_debug("  time:               %s", timeString);
+    }
+    tr_debug("  longitude:          %.6f", pData->longitude);
+    tr_debug("  latitude:           %.6f", pData->latitude);
+    tr_debug("  altitude:           %d metres", pData->altitude);
+    tr_debug("  uncertainty:        %d metres", pData->uncertainty);
+    tr_debug("  speed:              %d metres/second", pData->speed);
+    tr_debug("  vertical accuracy:  %d metres/second", pData->speed);
+    switch (pData->sensor) {
+        case UbloxATCellularInterfaceExt::CELL_LAST:
+            tr_debug("  sensor type:        last");
+            break;
+        case UbloxATCellularInterfaceExt::CELL_GNSS:
+            tr_debug("  sensor type:        GNSS");
+            break;
+        case UbloxATCellularInterfaceExt::CELL_LOCATE:
+            tr_debug("  sensor type:        Cell Locate");
+            break;
+        case UbloxATCellularInterfaceExt::CELL_HYBRID:
+            tr_debug("  sensor type:        hybrid");
+            break;
+        default:
+            tr_debug("  sensor type:        unknown");
+            break;
+    }
+    tr_debug("  satellites used:    %d", pData->svUsed);
+}
+
+// ----------------------------------------------------------------
+// TESTS
+// ----------------------------------------------------------------
+
+// Test Cell Locate talking to a UDP server
+void test_udp_server() {
+    int numRes = 0;
+    UbloxATCellularInterfaceExt::CellLocData data;
+
+    memset(&data, 0, sizeof(data));
+    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
+                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);
+
+    TEST_ASSERT(pDriver->cellLocSrvUdp());
+    TEST_ASSERT(pDriver->cellLocConfig(1));
+    TEST_ASSERT(pDriver->cellLocRequest(UbloxATCellularInterfaceExt::CELL_HYBRID, 10, 100,
+                                        (UbloxATCellularInterfaceExt::CellRespType) MBED_CONF_APP_RESP_TYPE,
+                                        MBED_CONF_APP_CELL_LOCATE_MAX_NUM_HYPOTHESIS));
+
+    for (int x = 0; (numRes == 0) && (x < 10); x++) {
+        numRes = pDriver->cellLocGetRes();
+    }
+
+    TEST_ASSERT(numRes > 0);
+    TEST_ASSERT(pDriver->cellLocGetData(&data));
+
+    TEST_ASSERT(data.validData);
+
+    printCellLocateData(&data);
+
+    TEST_ASSERT(pDriver->disconnect() == 0);
+    // Wait for printfs to leave the building or the test result string gets messed up
+    wait_ms(500);
+}
+
+// Test Cell Locate talking to a TCP server
+void test_tcp_server() {
+    int numRes = 0;
+    UbloxATCellularInterfaceExt::CellLocData data;
+
+    memset(&data, 0, sizeof(data));
+    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
+                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);
+
+    TEST_ASSERT(pDriver->cellLocSrvTcp(MBED_CONF_APP_CELL_LOCATE_MGA_TOKEN));
+    TEST_ASSERT(pDriver->cellLocConfig(1));
+    TEST_ASSERT(pDriver->cellLocRequest(UbloxATCellularInterfaceExt::CELL_HYBRID, 10, 100,
+                                        (UbloxATCellularInterfaceExt::CellRespType) MBED_CONF_APP_RESP_TYPE,
+                                        MBED_CONF_APP_CELL_LOCATE_MAX_NUM_HYPOTHESIS));
+
+    for (int x = 0; (numRes == 0) && (x < 10); x++) {
+        numRes = pDriver->cellLocGetRes();
+    }
+
+    TEST_ASSERT(numRes > 0);
+    TEST_ASSERT(pDriver->cellLocGetData(&data));
+
+    TEST_ASSERT(data.validData);
+
+    printCellLocateData(&data);
+
+    TEST_ASSERT(pDriver->disconnect() == 0);
+    // Wait for printfs to leave the building or the test result string gets messed up
+    wait_ms(500);
+}
+
+// Tidy up after testing so as not to screw with the test output strings
+void test_tidy_up() {
+    pDriver->deinit();
+}
+
+// ----------------------------------------------------------------
+// TEST ENVIRONMENT
+// ----------------------------------------------------------------
+
+// Setup the test environment
+utest::v1::status_t test_setup(const size_t number_of_cases) {
+    // Setup Greentea with a timeout
+    GREENTEA_SETUP(540, "default_auto");
+    return verbose_test_setup_handler(number_of_cases);
+}
+
+// Test cases
+Case cases[] = {
+    Case("Cell Locate with UDP server", test_udp_server),
+#if MBED_CONF_APP_RUN_CELL_LOCATE_TCP_SERVER_TEST
+    Case("Cell Locate with TCP server", test_tcp_server),
+#endif
+    Case("Tidy up", test_tidy_up)
+};
+
+Specification specification(test_setup, cases);
+
+// ----------------------------------------------------------------
+// MAIN
+// ----------------------------------------------------------------
+
+int main() {
+    mbed_trace_init();
+
+    mbed_trace_mutex_wait_function_set(lock);
+    mbed_trace_mutex_release_function_set(unlock);
+    
+    // Run tests
+    return !Harness::run(specification);
+}
+
+// End Of File
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TESTS/unit_tests/cell-locate/template_mbed_app.txt	Mon Jun 05 12:58:04 2017 +0000
@@ -0,0 +1,33 @@
+{
+    "config": {
+        "buffer-size": { "value": 0 },
+        "platform": { "help": "Options: UBLOX, MTS_DRAGONFLY",
+                      "value": "UBLOX"},
+        "cell-locate-mga-token": {
+            "help": "Your Cell Locate MGA token, required for TCP testing.  A tokane can be obtained from https://www.u-blox.com/en/assistnow-service-registration-form",
+            "value": "\"mymgatoken\""
+        },
+        "run-cell-locate-tcp-server-test": {
+            "help": "Set to true to run tests over TCP as well as over UDP",
+            "value": 1
+        }
+     },
+    "target_overrides": {
+        "*": {
+            "lwip.ipv4-enabled": true,
+            "lwip.ipv6-enabled": false,
+            "lwip.ethernet-enabled": false,
+            "lwip.ppp-enabled": true,
+            "lwip.tcp-enabled": true,
+            "target.features_add": ["LWIP", "COMMON_PAL"],
+            "platform.stdio-convert-newlines": true,
+            "platform.stdio-baud-rate": 9600,
+            "platform.default-serial-baud-rate": 115200, 
+            "lwip.debug-enabled": false,
+            "lwip.enable-ppp-trace": false,
+            "lwip.use-mbed-trace": false,
+            "mbed-trace.enable": 1
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TESTS/unit_tests/ftp/main.cpp	Mon Jun 05 12:58:04 2017 +0000
@@ -0,0 +1,529 @@
+#include "mbed.h"
+#include "greentea-client/test_env.h"
+#include "unity.h"
+#include "utest.h"
+#include "UbloxATCellularInterfaceExt.h"
+#include "UDPSocket.h"
+#include "FEATURE_COMMON_PAL/nanostack-libservice/mbed-client-libservice/common_functions.h"
+#include "mbed_trace.h"
+#define TRACE_GROUP "TEST"
+
+using namespace utest::v1;
+
+// ----------------------------------------------------------------
+// COMPILE-TIME MACROS
+// ----------------------------------------------------------------
+
+// These macros can be overridden with an mbed_app.json file and
+// contents of the following form:
+//
+//{
+//    "config": {
+//      "apn": {
+//          "value": "\"my_apn\""
+//      },
+//      "ftp-server": {
+//          "value": "\"test.rebex.net\""
+//      },
+//      "ftp-username": {
+//          "value": "\"demo\""
+//      },
+//      "ftp-password": {
+//          "value": "\"password\""
+//      },
+//      "ftp-use-passive": {
+//          "value": true
+//      },
+//      "ftp-server-supports-write": {
+//          "value": false
+//      },
+//      "ftp-filename": {
+//          "value": "\"readme.txt\""
+//      },
+//      "ftp-dirname": {
+//          "value": "\"pub\""
+//      }
+//}
+
+// The credentials of the SIM in the board.
+#ifndef MBED_CONF_APP_DEFAULT_PIN
+// Note: this is the PIN for the SIM with ICCID
+// 8944501104169548380.
+# define MBED_CONF_APP_DEFAULT_PIN "5134"
+#endif
+
+// Network credentials.
+#ifndef MBED_CONF_APP_APN
+# define MBED_CONF_APP_APN         NULL
+#endif
+#ifndef MBED_CONF_APP_USERNAME
+# define MBED_CONF_APP_USERNAME    NULL
+#endif
+#ifndef MBED_CONF_APP_PASSWORD
+# define MBED_CONF_APP_PASSWORD    NULL
+#endif
+
+// FTP server name
+#ifndef MBED_CONF_APP_FTP_SERVER
+# error "Must define an FTP server name to run these tests"
+#endif
+
+// User name on the FTP server
+#ifndef MBED_CONF_APP_FTP_USERNAME
+# define MBED_CONF_APP_FTP_SERVER_USERNAME ""
+#endif
+
+// Password on the FTP server
+#ifndef MBED_CONF_APP_FTP_PASSWORD
+# define MBED_CONF_APP_FTP_SERVER_PASSWORD ""
+#endif
+
+// Whether to use SFTP or not
+#ifndef MBED_CONF_APP_FTP_SECURE
+# define MBED_CONF_APP_FTP_SECURE false
+#endif
+
+// Port to use on the remote server
+#ifndef MBED_CONF_APP_FTP_SERVER_PORT
+# if MBED_CONF_APP_FTP_SECURE
+#   define MBED_CONF_APP_FTP_SERVER_PORT 22
+# else
+#   define MBED_CONF_APP_FTP_SERVER_PORT 21
+# endif
+#endif
+
+// Whether to use passive or active mode
+// default to true as many servers/networks
+// require this
+#ifndef MBED_CONF_APP_FTP_USE_PASSIVE
+# define MBED_CONF_APP_FTP_USE_PASSIVE true
+#endif
+
+// Whether the server supports FTP write operations
+#ifndef MBED_CONF_APP_FTP_SERVER_SUPPORTS_WRITE
+# define MBED_CONF_APP_FTP_SERVER_SUPPORTS_WRITE false
+#endif
+
+#if MBED_CONF_APP_FTP_SERVER_SUPPORTS_WRITE
+// The name of the file to PUT, GET and then delete
+# ifndef MBED_CONF_APP_FTP_FILENAME
+#  define MBED_CONF_APP_FTP_FILENAME "test_file_delete_me"
+# endif
+// The name of the directory to create, CD to and then remove
+# ifndef MBED_CONF_APP_FTP_DIRNAME
+#  define MBED_CONF_APP_FTP_DIRNAME "test_dir_delete_me"
+# endif
+#else
+// The name of the file to GET
+# ifndef MBED_CONF_APP_FTP_FILENAME
+# error "Must define the name of a file you know exists on the FTP server"
+# endif
+// The name of the directory to CD to
+# ifndef MBED_CONF_APP_FTP_DIRNAME
+# error "Must define the name of a directory you know exists on the FTP server"
+# endif
+#endif
+
+// The size of file when testing PUT/GET
+#ifndef MBED_CONF_APP_FTP_FILE_SIZE
+#  define MBED_CONF_APP_FTP_FILE_SIZE 42000
+#endif
+
+// ----------------------------------------------------------------
+// PRIVATE VARIABLES
+// ----------------------------------------------------------------
+
+// Lock for debug prints
+static Mutex mtx;
+
+// An instance of the cellular interface
+static UbloxATCellularInterfaceExt *pDriver =
+       new UbloxATCellularInterfaceExt(MDMTXD, MDMRXD,
+                                       MBED_CONF_UBLOX_CELL_BAUD_RATE,
+                                       true);
+// A buffer for general use
+static char buf[MBED_CONF_APP_FTP_FILE_SIZE];
+
+// ----------------------------------------------------------------
+// PRIVATE FUNCTIONS
+// ----------------------------------------------------------------
+
+// Locks for debug prints
+static void lock()
+{
+    mtx.lock();
+}
+
+static void unlock()
+{
+    mtx.unlock();
+}
+
+
+// Write a file to the module's file system with known contents
+void createFile(const char * filename) {
+
+    for (unsigned int x = 0; x < sizeof (buf); x++) {
+        buf[x] = (char) x;
+    }
+
+    TEST_ASSERT(pDriver->writeFile(filename, buf, sizeof (buf)) == sizeof (buf));
+    tr_debug("%d bytes written to file \"%s\"", sizeof (buf), filename);
+}
+
+// Read a file back from the module's file system and check the contents
+void checkFile(const char * filename) {
+    memset(buf, 0, sizeof (buf));
+
+    int x = pDriver->readFile(filename, buf, sizeof (buf));
+    tr_debug ("File is %d bytes big", x);
+    TEST_ASSERT(x == sizeof (buf));
+
+    tr_debug("%d bytes read from file \"%s\"", sizeof (buf), filename);
+
+    for (unsigned int x = 0; x < sizeof (buf); x++) {
+        TEST_ASSERT(buf[x] == (char) x);
+    }
+}
+
+// ----------------------------------------------------------------
+// TESTS
+// ----------------------------------------------------------------
+
+// Test the setting up of parameters, connection and login to an FTP session
+void test_ftp_login() {
+    SocketAddress address;
+    char portString[10];
+
+    sprintf(portString, "%d", MBED_CONF_APP_FTP_SERVER_PORT);
+
+    TEST_ASSERT(pDriver->init(MBED_CONF_APP_DEFAULT_PIN));
+
+    // Reset parameters to default to begin with
+    TEST_ASSERT(pDriver->ftpResetPar());
+
+    // Set a timeout for FTP commands
+    TEST_ASSERT(pDriver->ftpSetTimeout(60000));
+
+    // Set up the FTP server parameters
+    TEST_ASSERT(pDriver->ftpSetPar(UbloxATCellularInterfaceExt::FTP_SERVER_NAME,
+                                   MBED_CONF_APP_FTP_SERVER));
+    TEST_ASSERT(pDriver->ftpSetPar(UbloxATCellularInterfaceExt::FTP_SERVER_PORT,
+                                   portString));
+    TEST_ASSERT(pDriver->ftpSetPar(UbloxATCellularInterfaceExt::FTP_USER_NAME,
+                                   MBED_CONF_APP_FTP_USERNAME));
+    TEST_ASSERT(pDriver->ftpSetPar(UbloxATCellularInterfaceExt::FTP_PASSWORD,
+                                   MBED_CONF_APP_FTP_PASSWORD));
+#ifdef MBED_CONF_APP_FTP_ACCOUNT
+    TEST_ASSERT(pDriver->ftpSetPar(UbloxATCellularInterfaceExt::FTP_ACCOUNT,
+                                   MBED_CONF_APP_FTP_ACCOUNT));
+#endif
+#if MBED_CONF_APP_FTP_SECURE
+    TEST_ASSERT(pDriver->ftpSetPar(UbloxATCellularInterfaceExt::FTP_SECURE, "1"));
+#endif
+#if MBED_CONF_APP_FTP_USE_PASSIVE
+    TEST_ASSERT(pDriver->ftpSetPar(UbloxATCellularInterfaceExt::FTP_MODE, "1"));
+#endif
+
+    // Now connect to the network
+    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
+                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);
+
+    // Get the server IP address, purely to make sure it's there
+    TEST_ASSERT(pDriver->gethostbyname(MBED_CONF_APP_FTP_SERVER, &address) == 0);
+    tr_debug ("Using FTP \"%s\", which is at %s", MBED_CONF_APP_FTP_SERVER,
+              address.get_ip_address());
+
+    // Log into the FTP server
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LOGIN) == NULL);
+}
+
+// Test FTP directory listing
+void test_ftp_dir() {
+    // Get a directory listing
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The file we will GET should appear in the directory listing
+    TEST_ASSERT(strstr(buf, MBED_CONF_APP_FTP_FILENAME) > NULL);
+    // As should the directory name we will change to
+    TEST_ASSERT(strstr(buf, MBED_CONF_APP_FTP_DIRNAME) > NULL);
+}
+
+// Test FTP file information
+void test_ftp_fileinfo() {
+    // Get the info
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_FILE_INFO,
+                                    MBED_CONF_APP_FTP_FILENAME, NULL, 0,
+                                    buf, sizeof (buf)) == NULL);
+    tr_debug("File info:\n%s", buf);
+
+    // The file info string should at least include the file name
+    TEST_ASSERT(strstr(buf, MBED_CONF_APP_FTP_FILENAME) > NULL);
+}
+
+#if MBED_CONF_APP_FTP_SERVER_SUPPORTS_WRITE
+
+// In case a previous test failed half way, do some cleaning up first
+// Note: don't check return values as these operations will fail
+// if there's nothing to clean up
+void test_ftp_write_cleanup() {
+    pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_DELETE_FILE,
+                        MBED_CONF_APP_FTP_FILENAME);
+    pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_DELETE_FILE,
+                        MBED_CONF_APP_FTP_FILENAME "_2");
+    pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_RMDIR,
+                        MBED_CONF_APP_FTP_DIRNAME);
+    pDriver->delFile(MBED_CONF_APP_FTP_FILENAME);
+    pDriver->delFile(MBED_CONF_APP_FTP_FILENAME "_1");
+}
+
+// Test FTP put and then get
+void test_ftp_put_get() {
+    // Create the file
+    createFile(MBED_CONF_APP_FTP_FILENAME);
+
+    // Put the file
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_PUT_FILE,
+                                    MBED_CONF_APP_FTP_FILENAME) == NULL);
+
+    // Get the file
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_GET_FILE,
+                                    MBED_CONF_APP_FTP_FILENAME,
+                                    MBED_CONF_APP_FTP_FILENAME "_1") == NULL);
+
+    // Check that it is the same as we sent
+    checkFile(MBED_CONF_APP_FTP_FILENAME "_1");
+}
+
+// Test FTP rename file
+void test_ftp_rename() {
+    // Get a directory listing
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The file we are renaming to should not appear
+    TEST_ASSERT(strstr(buf,  MBED_CONF_APP_FTP_FILENAME "_2") == NULL);
+
+    // Rename the file
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_RENAME_FILE,
+                                    MBED_CONF_APP_FTP_FILENAME,
+                                    MBED_CONF_APP_FTP_FILENAME "_2") == NULL);
+
+    // Get a directory listing
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The new file should now exist
+    TEST_ASSERT(strstr(buf,  MBED_CONF_APP_FTP_FILENAME "_2") > NULL);
+
+}
+
+// Test FTP delete file
+void test_ftp_delete() {
+    // Get a directory listing
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The file we are to delete should appear in the list
+    TEST_ASSERT(strstr(buf,  MBED_CONF_APP_FTP_FILENAME "_2") > NULL);
+
+    // Delete the file
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_DELETE_FILE,
+                                    MBED_CONF_APP_FTP_FILENAME "_2") == NULL);
+
+    // Get a directory listing
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The file we deleted should no longer appear in the list
+    TEST_ASSERT(strstr(buf,  MBED_CONF_APP_FTP_FILENAME "_2") == NULL);
+}
+
+// Test FTP MKDIR
+void test_ftp_mkdir() {
+    // Get a directory listing
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The directory we are to create should not appear in the list
+    TEST_ASSERT(strstr(buf,  MBED_CONF_APP_FTP_DIRNAME) == NULL);
+
+    // Create the directory
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_MKDIR,
+                                    MBED_CONF_APP_FTP_DIRNAME) == NULL);
+
+    // Get a directory listing
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The directory we created should now appear in the list
+    TEST_ASSERT(strstr(buf,  MBED_CONF_APP_FTP_DIRNAME) > NULL);
+}
+
+// Test FTP RMDIR
+void test_ftp_rmdir() {
+    // Get a directory listing
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The directory we are to remove should appear in the list
+    TEST_ASSERT(strstr(buf,  MBED_CONF_APP_FTP_DIRNAME) > NULL);
+
+    // Remove the directory
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_RMDIR,
+                                    MBED_CONF_APP_FTP_DIRNAME) == NULL);
+
+    // Get a directory listing
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The directory we removed should no longer appear in the list
+    TEST_ASSERT(strstr(buf,  MBED_CONF_APP_FTP_DIRNAME) == NULL);
+}
+
+#endif // MBED_CONF_APP_FTP_SERVER_SUPPORTS_WRITE
+
+// Test FTP get
+void test_ftp_get() {
+    // Make sure that the 'get' filename we're going to use
+    // isn't already here (but don't assert on this one
+    // as, if the file isn't there, we will get an error)
+    pDriver->delFile(MBED_CONF_APP_FTP_FILENAME);
+
+    // Get the file
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_GET_FILE,
+                                    MBED_CONF_APP_FTP_FILENAME) == NULL);
+
+    // Check that it has arrived
+    TEST_ASSERT(pDriver->fileSize(MBED_CONF_APP_FTP_FILENAME) > 0);
+}
+
+// Test FTP change directory
+void test_ftp_cd() {
+    // Get a directory listing
+    *buf = 0;
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+
+    tr_debug("Listing:\n%s", buf);
+
+    // The listing should include the directory name we are going to move to
+    TEST_ASSERT(strstr(buf, MBED_CONF_APP_FTP_DIRNAME) > NULL);
+
+    // Change directories
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_CD,
+                                    MBED_CONF_APP_FTP_DIRNAME) == NULL);
+    // Get a directory listing
+    *buf = 0;
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The listing should no longer include the directory name we have moved
+    TEST_ASSERT(strstr(buf, MBED_CONF_APP_FTP_DIRNAME) == NULL);
+
+    // Go back to where we were
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_CD, "..")
+                == NULL);
+
+    // Get a directory listing
+    *buf = 0;
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LS,
+                                    NULL, NULL, 0, buf, sizeof (buf)) == NULL);
+    tr_debug("Listing:\n%s", buf);
+
+    // The listing should include the directory name we went to once more
+    TEST_ASSERT(strstr(buf, MBED_CONF_APP_FTP_DIRNAME) > NULL);
+}
+
+#ifdef MBED_CONF_APP_FTP_FOTA_FILENAME
+// Test FTP FOTA
+// TODO: test not tested as I don't have a module that supports the FTP FOTA operation
+void test_ftp_fota() {
+    *buf = 0;
+    // Do FOTA on a file
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_FOTA_FILE,
+                                    MBED_CONF_APP_FTP_FOTA_FILENAME, NULL,
+                                    0, buf, sizeof (buf)) == NULL);
+    tr_debug("MD5 sum: %s\n", buf);
+
+    // Check that the 128 bit MD5 sum is now there
+    TEST_ASSERT(strlen (buf) == 32);
+}
+#endif
+
+// Test logout and disconnect from an FTP session
+void test_ftp_logout() {
+    // Log out from the FTP server
+    TEST_ASSERT(pDriver->ftpCommand(UbloxATCellularInterfaceExt::FTP_LOGOUT) == NULL);
+
+    TEST_ASSERT(pDriver->disconnect() == 0);
+
+    // Wait for printfs to leave the building or the test result string gets messed up
+    wait_ms(500);
+}
+
+// ----------------------------------------------------------------
+// TEST ENVIRONMENT
+// ----------------------------------------------------------------
+
+// Setup the test environment
+utest::v1::status_t test_setup(const size_t number_of_cases) {
+    // Setup Greentea with a timeout
+    GREENTEA_SETUP(540, "default_auto");
+    return verbose_test_setup_handler(number_of_cases);
+}
+
+// Test cases
+Case cases[] = {
+    Case("FTP log in", test_ftp_login),
+#if MBED_CONF_APP_FTP_SERVER_SUPPORTS_WRITE
+    Case("Clean-up", test_ftp_write_cleanup),
+    Case("FTP put and get", test_ftp_put_get),
+    Case("FTP file info", test_ftp_fileinfo),
+    Case("FTP rename", test_ftp_rename),
+    Case("FTP make directory", test_ftp_mkdir),
+    Case("FTP directory list", test_ftp_dir),
+    Case("FTP delete", test_ftp_delete),
+    Case("FTP change directory", test_ftp_cd),
+    Case("FTP delete directory", test_ftp_rmdir),
+#else
+    Case("FTP directory list", test_ftp_dir),
+    Case("FTP file info", test_ftp_fileinfo),
+    Case("FTP get", test_ftp_get),
+    Case("FTP change directory", test_ftp_cd),
+#endif
+#ifdef MBED_CONF_APP_FTP_FOTA_FILENAME
+    Case("FTP FOTA", test_ftp_fota),
+#endif
+    Case("FTP log out", test_ftp_logout)
+};
+
+Specification specification(test_setup, cases);
+
+// ----------------------------------------------------------------
+// MAIN
+// ----------------------------------------------------------------
+
+int main() {
+    mbed_trace_init();
+
+    mbed_trace_mutex_wait_function_set(lock);
+    mbed_trace_mutex_release_function_set(unlock);
+    
+    // Run tests
+    return !Harness::run(specification);
+}
+
+// End Of File
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TESTS/unit_tests/ftp/template_mbed_app.txt	Mon Jun 05 12:58:04 2017 +0000
@@ -0,0 +1,53 @@
+{
+    "config": {
+        "buffer-size": { "value": 0 },
+        "platform": { "help": "Options: UBLOX, MTS_DRAGONFLY",
+                      "value": "UBLOX"},
+        "ftp-server": {
+            "help": "An FTP server to use when running the FTP tests",
+            "value": "\"test.rebex.net\""
+        },
+        "ftp-username": {
+            "help": "The user name for the FTP server account",
+            "value": "\"demo\""
+        },
+        "ftp-password": {
+            "help": "The password for the FTP server account",
+            "value": "\"password\""
+        },
+        "ftp-use-passive": {
+            "help": "Set to true to use passive mode, otherwise false (defaults to true, since this is needed for most cases)",
+            "value": true
+        },
+        "ftp-server-supports-write": {
+            "help": "Set to true if the FTP server supports PUT, rename, MKDIR and delete, otherwise set to false",
+            "value": false
+        },
+        "ftp-filename": {
+            "help": "A filename to use during FTP tests.  This file must already exist on the server if the server does not support write",
+            "value": "\"readme.txt\""
+        },
+        "ftp-dirname": {
+            "help": "A directory name to yse during FTP tests.  This directory must already exist on the server if the server does not support write",
+            "value": "\"pub\""
+        }
+     },
+    "target_overrides": {
+        "*": {
+            "lwip.ipv4-enabled": true,
+            "lwip.ipv6-enabled": false,
+            "lwip.ethernet-enabled": false,
+            "lwip.ppp-enabled": true,
+            "lwip.tcp-enabled": true,
+            "target.features_add": ["LWIP", "COMMON_PAL"],
+            "platform.stdio-convert-newlines": true,
+            "platform.stdio-baud-rate": 9600,
+            "platform.default-serial-baud-rate": 115200, 
+            "lwip.debug-enabled": false,
+            "lwip.enable-ppp-trace": false,
+            "lwip.use-mbed-trace": false,
+            "mbed-trace.enable": 1
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TESTS/unit_tests/http/main.cpp	Mon Jun 05 12:58:04 2017 +0000
@@ -0,0 +1,335 @@
+#include "mbed.h"
+#include "greentea-client/test_env.h"
+#include "unity.h"
+#include "utest.h"
+#include "UbloxATCellularInterfaceExt.h"
+#include "UDPSocket.h"
+#include "FEATURE_COMMON_PAL/nanostack-libservice/mbed-client-libservice/common_functions.h"
+#include "mbed_trace.h"
+#define TRACE_GROUP "TEST"
+
+using namespace utest::v1;
+
+// ----------------------------------------------------------------
+// COMPILE-TIME MACROS
+// ----------------------------------------------------------------
+
+// These macros can be overridden with an mbed_app.json file and
+// contents of the following form:
+//
+//{
+//    "config": {
+//        "apn": {
+//            "value": "\"my_apn\""
+//        }
+//}
+
+// The credentials of the SIM in the board.
+#ifndef MBED_CONF_APP_DEFAULT_PIN
+// Note: this is the PIN for the SIM with ICCID
+// 8944501104169548380.
+# define MBED_CONF_APP_DEFAULT_PIN "5134"
+#endif
+
+// Network credentials.
+#ifndef MBED_CONF_APP_APN
+# define MBED_CONF_APP_APN         NULL
+#endif
+#ifndef MBED_CONF_APP_USERNAME
+# define MBED_CONF_APP_USERNAME    NULL
+#endif
+#ifndef MBED_CONF_APP_PASSWORD
+# define MBED_CONF_APP_PASSWORD    NULL
+#endif
+
+// The time to wait for a HTTP command to complete
+#define HTTP_TIMEOUT  10000
+
+// The HTTP echo server, as described in the
+// first answer here:
+// http://stackoverflow.com/questions/5725430/http-test-server-that-accepts-get-post-calls
+// !!! IMPORTANT: this test relies on that server behaving in the same way forever !!!
+#define HTTP_ECHO_SERVER "httpbin.org"
+
+// The size of the test file
+#define TEST_FILE_SIZE 100
+
+// The maximum number of HTTP profiles
+#define MAX_PROFILES 4
+
+// ----------------------------------------------------------------
+// PRIVATE VARIABLES
+// ----------------------------------------------------------------
+
+// Lock for debug prints
+static Mutex mtx;
+
+// An instance of the cellular interface
+static UbloxATCellularInterfaceExt *pDriver =
+       new UbloxATCellularInterfaceExt(MDMTXD, MDMRXD,
+                                       MBED_CONF_UBLOX_CELL_BAUD_RATE,
+                                       true);
+// A few buffers for general use
+static char buf[1024];
+static char buf1[sizeof(buf)];
+
+// ----------------------------------------------------------------
+// PRIVATE FUNCTIONS
+// ----------------------------------------------------------------
+
+// Locks for debug prints
+static void lock()
+{
+    mtx.lock();
+}
+
+static void unlock()
+{
+    mtx.unlock();
+}
+
+// ----------------------------------------------------------------
+// TESTS
+// ----------------------------------------------------------------
+
+// Test HTTP commands
+void test_http_cmd() {
+    int profile;
+    char * pData;
+
+    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
+                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);
+
+    profile = pDriver->httpAllocProfile();
+    TEST_ASSERT(profile >= 0);
+
+    pDriver->httpSetTimeout(profile, HTTP_TIMEOUT);
+
+    // Set up the server to talk to
+    TEST_ASSERT(pDriver->httpSetPar(profile, UbloxATCellularInterfaceExt::HTTP_SERVER_NAME, HTTP_ECHO_SERVER));
+
+    // Check HTTP head request
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_HEAD,
+                                     "/headers",
+                                     NULL, NULL, 0, NULL,
+                                     buf, sizeof (buf)) == NULL);
+    tr_debug("Received: %s", buf);
+    TEST_ASSERT(strstr(buf, "Content-Length:") != NULL);
+
+    // Check HTTP get request
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_GET,
+                                     "/get",
+                                     NULL, NULL, 0, NULL,
+                                     buf, sizeof (buf)) == NULL);
+    tr_debug("Received: %s", buf);
+    TEST_ASSERT(strstr(buf, "\"http://httpbin.org/get\"") != NULL);
+
+    // Check HTTP delete request
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_DELETE,
+                                     "/delete",
+                                     NULL, NULL, 0, NULL,
+                                     buf, sizeof (buf)) == NULL);
+    tr_debug("Received: %s", buf);
+    TEST_ASSERT(strstr(buf, "\"http://httpbin.org/delete\"") != NULL);
+
+    // Check HTTP put request (this will fail as the echo server doesn't support it)
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_PUT,
+                                     "/put",
+                                     NULL, NULL, 0, NULL,
+                                     buf, sizeof (buf)) != NULL);
+
+    // Check HTTP post request with data
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_POST_DATA,
+                                     "/post",
+                                     NULL, NULL, 0, NULL,
+                                     buf, sizeof (buf)) == NULL);
+    tr_debug("Received: %s", buf);
+    TEST_ASSERT(strstr(buf, "\"http://httpbin.org/post\"") != NULL);
+
+    // Check HTTP post request with a file, also checking that writing the response
+    // to a named file works
+    for (int x = 0; x < TEST_FILE_SIZE; x++) {
+        buf[x] = (x % 10) + 0x30;
+    }
+    pDriver->delFile("post_test.txt");
+    TEST_ASSERT(pDriver->writeFile("post_test.txt", buf, TEST_FILE_SIZE) == TEST_FILE_SIZE);
+
+    // This may fail if rsp.txt doesn't happen to be sitting around from a previous run
+    // so don't check the return value
+    pDriver->delFile("rsp.txt");
+
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_POST_FILE,
+                                     "/post",
+                                     "rsp.txt", "post_test.txt",
+                                     UbloxATCellularInterfaceExt::HTTP_CONTENT_TEXT, NULL,
+                                     buf, sizeof (buf)) == NULL);
+    tr_debug("Received: %s", buf);
+    // Find the data in the response and check it
+    pData = strstr(buf, "\"data\": \"");
+    TEST_ASSERT(pData != NULL);
+    pData += 9;
+    for (int x = 0; x < TEST_FILE_SIZE; x++) {
+        TEST_ASSERT(*(pData + x) == (x % 10) + 0x30);
+    }
+
+    // Also check that rsp.txt exists and is the same as buf
+    pDriver->readFile("rsp.txt", buf1, sizeof (buf1));
+    memcmp(buf1, buf, sizeof (buf1));
+    TEST_ASSERT(pDriver->delFile("rsp.txt"));
+    TEST_ASSERT(!pDriver->delFile("rsp.txt")); // Should fail
+
+    TEST_ASSERT(pDriver->httpFreeProfile(profile));
+    TEST_ASSERT(pDriver->disconnect() == 0);
+    // Wait for printfs to leave the building or the test result string gets messed up
+    wait_ms(500);
+}
+
+// Test HTTP with TLS
+void test_http_tls() {
+    int profile;
+    SocketAddress address;
+
+    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
+                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);
+
+    profile = pDriver->httpAllocProfile();
+    TEST_ASSERT(profile >= 0);
+
+    pDriver->httpSetTimeout(profile, HTTP_TIMEOUT);
+
+    // Set up the server to talk to and TLS, using the IP address this time just for variety
+    TEST_ASSERT(pDriver->gethostbyname("amazon.com", &address) == 0);
+    TEST_ASSERT(pDriver->httpSetPar(profile, UbloxATCellularInterfaceExt::HTTP_IP_ADDRESS, address.get_ip_address()));
+    TEST_ASSERT(pDriver->httpSetPar(profile, UbloxATCellularInterfaceExt::HTTP_SECURE, "1"));
+
+    // Check HTTP get request
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_GET,
+                                     "/",
+                                     NULL, NULL, 0, NULL,
+                                     buf, sizeof (buf)) == NULL);
+    tr_debug("Received: %s", buf);
+    // This is what amazon.com returns if TLS is set
+    TEST_ASSERT(strstr(buf, "302 MovedTemporarily") != NULL);
+
+    // Reset the profile and check that this now fails
+    TEST_ASSERT(pDriver->httpResetProfile(profile));
+    TEST_ASSERT(pDriver->httpSetPar(profile, UbloxATCellularInterfaceExt::HTTP_IP_ADDRESS, address.get_ip_address()));
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profile, UbloxATCellularInterfaceExt::HTTP_GET,
+                                     "/",
+                                     NULL, NULL, 0, NULL,
+                                     buf, sizeof (buf)) == NULL);
+    tr_debug("Received: %s", buf);
+    // This is what amazon.com returns if TLS is NOT set
+    TEST_ASSERT(strstr(buf, "301 Moved Permanently") != NULL);
+
+    TEST_ASSERT(pDriver->httpFreeProfile(profile));
+    TEST_ASSERT(pDriver->disconnect() == 0);
+    // Wait for printfs to leave the building or the test result string gets messed up
+    wait_ms(500);
+}
+
+// Allocate max profiles
+void test_alloc_profiles() {
+    int profiles[MAX_PROFILES];
+
+    TEST_ASSERT(pDriver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN,
+                                 MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0);
+
+    // Allocate first profile and use it
+    profiles[0] = pDriver->httpAllocProfile();
+    TEST_ASSERT(profiles[0] >= 0);
+    TEST_ASSERT(pDriver->httpSetPar(profiles[0], UbloxATCellularInterfaceExt::HTTP_SERVER_NAME, "developer.mbed.org"));
+
+    // Check HTTP get request
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profiles[0], UbloxATCellularInterfaceExt::HTTP_GET,
+                                     "/media/uploads/mbed_official/hello.txt",
+                                     NULL, NULL, 0, NULL,
+                                     buf, sizeof (buf)) == NULL);
+    tr_debug("Received: %s", buf);
+    TEST_ASSERT(strstr(buf, "Hello world!") != NULL);
+
+    // Check that we stop being able to get profiles at the max number
+    for (int x = 1; x < sizeof (profiles) / sizeof (profiles[0]); x++) {
+        profiles[x] = pDriver->httpAllocProfile();
+        TEST_ASSERT(profiles[0] >= 0);
+    }
+    TEST_ASSERT(pDriver->httpAllocProfile() < 0);
+
+    // Now use the last one and check that it doesn't affect the first one
+    TEST_ASSERT(pDriver->httpSetPar(profiles[sizeof (profiles) / sizeof (profiles[0]) - 1],
+                                    UbloxATCellularInterfaceExt::HTTP_SERVER_NAME, HTTP_ECHO_SERVER));
+
+    // Check HTTP head request on last profile
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profiles[sizeof (profiles) / sizeof (profiles[0]) - 1],
+                                     UbloxATCellularInterfaceExt::HTTP_HEAD,
+                                     "/headers",
+                                     NULL, NULL, 0, NULL,
+                                     buf, sizeof (buf)) == NULL);
+    tr_debug("Received: %s", buf);
+    TEST_ASSERT(strstr(buf, "Content-Length:") != NULL);
+
+    // Check HTTP get request on first profile once more
+    memset(buf, 0, sizeof (buf));
+    TEST_ASSERT(pDriver->httpCommand(profiles[0], UbloxATCellularInterfaceExt::HTTP_GET,
+                                     "/media/uploads/mbed_official/hello.txt",
+                                     NULL, NULL, 0, NULL,
+                                     buf, sizeof (buf)) == NULL);
+    tr_debug("Received: %s", buf);
+    TEST_ASSERT(strstr(buf, "Hello world!") != NULL);
+
+    // Free the profiles again
+    for (int x = 0; x < sizeof (profiles) / sizeof (profiles[0]); x++) {
+        TEST_ASSERT(pDriver->httpFreeProfile(profiles[x]));
+    }
+
+    TEST_ASSERT(pDriver->disconnect() == 0);
+    // Wait for printfs to leave the building or the test result string gets messed up
+    wait_ms(500);
+}
+
+// ----------------------------------------------------------------
+// TEST ENVIRONMENT
+// ----------------------------------------------------------------
+
+// Setup the test environment
+utest::v1::status_t test_setup(const size_t number_of_cases) {
+    // Setup Greentea with a timeout
+    GREENTEA_SETUP(540, "default_auto");
+    return verbose_test_setup_handler(number_of_cases);
+}
+
+// Test cases
+Case cases[] = {
+    Case("HTTP commands", test_http_cmd),
+    Case("HTTP with TLS", test_http_tls),
+    Case("Alloc max profiles", test_alloc_profiles)
+};
+
+Specification specification(test_setup, cases);
+
+// ----------------------------------------------------------------
+// MAIN
+// ----------------------------------------------------------------
+
+int main() {
+    mbed_trace_init();
+
+    mbed_trace_mutex_wait_function_set(lock);
+    mbed_trace_mutex_release_function_set(unlock);
+    
+    // Run tests
+    return !Harness::run(specification);
+}
+
+// End Of File
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UbloxATCellularInterfaceExt.cpp	Mon Jun 05 12:58:04 2017 +0000
@@ -0,0 +1,984 @@
+/* Copyright (c) 2017 ublox Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "UbloxATCellularInterfaceExt.h"
+#include "APN_db.h"
+#if defined(FEATURE_COMMON_PAL)
+#include "mbed_trace.h"
+#define TRACE_GROUP "UCAD"
+#else
+#define debug_if(_debug_trace_on, ...) (void(0)) // dummies if feature common pal is not added
+#define tr_info(...)  (void(0)) // dummies if feature common pal is not added
+#define tr_error(...) (void(0)) // dummies if feature common pal is not added
+#endif
+
+/**********************************************************************
+ * PROTECTED METHODS: HTTP
+ **********************************************************************/
+
+// Callback for HTTP result code handling.
+void UbloxATCellularInterfaceExt::UUHTTPCR_URC()
+{
+    char buf[32];
+    int a, b, c;
+
+    // Note: not calling _at->recv() from here as we're
+    // already in an _at->recv()
+    // +UUHTTPCR: <profile_id>,<op_code>,<param_val>
+    if (read_at_to_char(buf, sizeof (buf), '\n') > 0) {
+        if (sscanf(buf, ": %d,%d,%d", &a, &b, &c) == 3) {
+            _httpProfiles[a].cmd = b;          // Command
+            _httpProfiles[a].result = c;       // Result
+            debug_if(_debug_trace_on, "%s on profile %d, result code is %d\n", getHttpCmd((HttpCmd) b), a, c);
+        }
+    }
+}
+
+// Find a given profile.  NOTE: LOCK() before calling.
+int UbloxATCellularInterfaceExt::findProfile(int modemHandle)
+{
+    for (unsigned int profile = 0; profile < (sizeof(_httpProfiles)/sizeof(_httpProfiles[0]));
+         profile++) {
+        if (_httpProfiles[profile].modemHandle == modemHandle) {
+            return profile;
+        }
+    }
+
+    return HTTP_PROF_UNUSED;
+}
+
+// Return a string representing an HTTP AT command.
+const char *UbloxATCellularInterfaceExt::getHttpCmd(HttpCmd httpCmd)
+{
+    const char * str = "HTTP command not recognised";
+
+    switch (httpCmd) {
+        case HTTP_HEAD:
+            str = "HTTP HEAD command";
+            break;
+        case HTTP_GET:
+            str = "HTTP GET command";
+            break;
+        case HTTP_DELETE:
+            str = "HTTP DELETE command";
+            break;
+        case HTTP_PUT:
+            str = "HTTP PUT command";
+            break;
+        case HTTP_POST_FILE:
+            str = "HTTP POST file command";
+            break;
+        case HTTP_POST_DATA:
+            str = "HTTP POST data command";
+            break;
+        default:
+            break;
+    }
+
+    return str;
+}
+
+/**********************************************************************
+ * PROTECTED METHODS: FTP
+ **********************************************************************/
+
+// Callback for FTP result code handling.
+void UbloxATCellularInterfaceExt::UUFTPCR_URC()
+{
+    char buf[64];
+    char md5[32];
+
+    // Note: not calling _at->recv() from here as we're
+    // already in an _at->recv()
+    // +UUFTPCR: <op_code>,<ftp_result>[,<md5_sum>]
+    if (read_at_to_char(buf, sizeof (buf), '\n') > 0) {
+        if (sscanf(buf, ": %d,%d,%32[^\n]\n", &_lastFtpOpCodeResult, &_lastFtpResult, md5) == 3) {
+            // Store the MD5 sum if we can
+            if ((_ftpBuf != NULL) && (_ftpBufLen >= 32)) {
+                memcpy (_ftpBuf, md5, 32);
+                if (_ftpBufLen == 33) {
+                    *(buf + 32) = 0; // Add a terminator if there's room
+                }
+            }
+        }
+        debug_if(_debug_trace_on, "%s result code is %d\n",
+                 getFtpCmd((FtpCmd) _lastFtpOpCodeResult), _lastFtpResult);
+    }
+}
+
+// Callback for FTP data handling.
+void UbloxATCellularInterfaceExt::UUFTPCD_URC()
+{
+    char buf[32];
+    char *ftpBufPtr = _ftpBuf;
+    int ftpDataLen;
+
+    // Note: not calling _at->recv() from here as we're
+    // already in an _at->recv()
+    // +UUFTPCD: <op_code>,<ftp_data_len>,<ftp_data_in_quotes>
+    if (read_at_to_char(buf, sizeof(buf), '\"') > 0) {
+        if (sscanf(buf, ": %d,%d,\"", &_lastFtpOpCodeData, &ftpDataLen) == 2) {
+            if ((ftpBufPtr != NULL) && (_ftpBufLen > 0)) {
+                if (ftpDataLen + 1 > _ftpBufLen) { // +1 for terminator
+                    ftpDataLen = _ftpBufLen - 1;
+                }
+                ftpBufPtr += _at->read(ftpBufPtr, ftpDataLen);
+                *ftpBufPtr = 0; // Add terminator
+            }
+        }
+    }
+}
+
+// Return a string representing an FTP AT command.
+const char *UbloxATCellularInterfaceExt::getFtpCmd(FtpCmd ftpCmd)
+{
+    const char * str = "FTP command not recognised";
+
+    switch (ftpCmd) {
+        case FTP_LOGOUT:
+            str = "FTP log out command";
+            break;
+        case FTP_LOGIN:
+            str = "FTP log in command";
+            break;
+        case FTP_DELETE_FILE:
+            str = "FTP delete file command";
+            break;
+        case FTP_RENAME_FILE:
+            str = "FTP rename file command";
+            break;
+        case FTP_GET_FILE:
+            str = "FTP get file command";
+            break;
+        case FTP_PUT_FILE:
+            str = "FTP put file command";
+            break;
+        case FTP_CD:
+            str = "FTP change directory command";
+            break;
+        case FTP_MKDIR:
+            str = "FTP make directory command";
+            break;
+        case FTP_RMDIR:
+            str = "FTP remove directory command";
+            break;
+        case FTP_FILE_INFO:
+            str = "FTP file info command";
+            break;
+        case FTP_LS:
+            str = "FTP directory list command";
+            break;
+        case FTP_FOTA_FILE:
+            str = "FTP FOTA file command";
+            break;
+        default:
+            break;
+    }
+
+    return str;
+}
+
+/**********************************************************************
+ * PROTECTED METHODS: Cell Locate
+ **********************************************************************/
+
+// Callback for UULOCIND handling.
+void UbloxATCellularInterfaceExt::UULOCIND_URC()
+{
+    char buf[32];
+    int a, b;
+
+    // Note: not calling _at->recv() from here as we're
+    // already in an _at->recv()
+    // +UULOCIND: <step>,<result>
+    if (read_at_to_char(buf, sizeof (buf), '\n') > 0) {
+        if (sscanf(buf, " %d,%d", &a, &b) == 2) {
+            switch (a) {
+                case 0:
+                    debug_if(_debug_trace_on, "Network scan start\n");
+                    break;
+                case 1:
+                    debug_if(_debug_trace_on, "Network scan end\n");
+                    break;
+                case 2:
+                    debug_if(_debug_trace_on, "Requesting data from server\n");
+                    break;
+                case 3:
+                    debug_if(_debug_trace_on, "Received data from server\n");
+                    break;
+                case 4:
+                    debug_if(_debug_trace_on, "Sending feedback to server\n");
+                    break;
+                default:
+                    debug_if(_debug_trace_on, "Unknown step\n");
+                    break;
+            }
+            switch (b) {
+                case 0:
+                    // No error
+                    break;
+                case 1:
+                    debug_if(_debug_trace_on, "Wrong URL!\n");
+                    break;
+                case 2:
+                    debug_if(_debug_trace_on, "HTTP error!\n");
+                    break;
+                case 3:
+                    debug_if(_debug_trace_on, "Create socket error!\n");
+                    break;
+                case 4:
+                    debug_if(_debug_trace_on, "Close socket error!\n");
+                    break;
+                case 5:
+                    debug_if(_debug_trace_on, "Write to socket error!\n");
+                    break;
+                case 6:
+                    debug_if(_debug_trace_on, "Read from socket error!\n");
+                    break;
+                case 7:
+                    debug_if(_debug_trace_on, "Connection/DNS error!\n");
+                    break;
+                case 8:
+                    debug_if(_debug_trace_on, "Authentication token problem!\n");
+                    break;
+                case 9:
+                    debug_if(_debug_trace_on, "Generic error!\n");
+                    break;
+                case 10:
+                    debug_if(_debug_trace_on, "User terminated!\n");
+                    break;
+                case 11:
+                    debug_if(_debug_trace_on, "No data from server!\n");
+                    break;
+                default:
+                    debug_if(_debug_trace_on, "Unknown result!\n");
+                    break;
+            }
+        }
+    }
+}
+
+// Callback for UULOC URC handling.
+void UbloxATCellularInterfaceExt::UULOC_URC()
+{
+    int a, b;
+
+    // Note: not calling _at->recv() from here as we're
+    // already in an _at->recv()
+
+    // +UHTTPCR: <profile_id>,<op_code>,<param_val>
+    if (read_at_to_char(urcBuf, sizeof (urcBuf), '\n') > 0) {
+        // +UULOC: <date>,<time>,<lat>,<long>,<alt>,<uncertainty>,<speed>, <direction>,<vertical_acc>,<sensor_used>,<SV_used>,<antenna_status>, <jamming_status>
+        if (sscanf(urcBuf, ": %d/%d/%d,%d:%d:%d.%*d,%f,%f,%d,%d,%d,%d,%d,%d,%d,%*d,%*d",
+                   &_loc[0].time.tm_mday, &_loc[0].time.tm_mon,
+                   &_loc[0].time.tm_year, &_loc[0].time.tm_hour,
+                   &_loc[0].time.tm_min, &_loc[0].time.tm_sec,
+                   &_loc[0].latitude, &_loc[0].longitude, &_loc[0].altitude,
+                   &_loc[0].uncertainty, &_loc[0].speed, &_loc[0].direction,
+                   &_loc[0].verticalAcc,
+                   &b, &_loc[0].svUsed) == 15) {
+            debug_if(_debug_trace_on, "Position found at index 0\n");
+            _loc[0].sensor = (b == 0) ? CELL_LAST : (b == 1) ? CELL_GNSS :
+                             (b == 2) ? CELL_LOCATE : (b == 3) ? CELL_HYBRID : CELL_LAST;
+            _loc[0].time.tm_mon -= 1;
+            _loc[0].time.tm_wday = 0;
+            _loc[0].time.tm_yday = 0;
+            _loc[0].validData = true;
+            _locExpPos=1;
+            _locRcvPos++;
+        // +UULOC: <sol>,<num>,<sensor_used>,<date>,<time>,<lat>,<long>,<alt>,<uncertainty>,<speed>, <direction>,<vertical_acc>,,<SV_used>,<antenna_status>, <jamming_status>
+        } else if (sscanf(urcBuf, ": %d,%d,%d,%d/%d/%d,%d:%d:%d.%*d,%f,%f,%d,%d,%d,%d,%d,%d,%*d,%*d",
+                   &a, &_locExpPos, &b,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_mday,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_mon,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_year,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_hour,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_min,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_sec,
+                   &_loc[CELL_MAX_HYP - 1].latitude,
+                   &_loc[CELL_MAX_HYP - 1].longitude,
+                   &_loc[CELL_MAX_HYP - 1].altitude,
+                   &_loc[CELL_MAX_HYP - 1].uncertainty,
+                   &_loc[CELL_MAX_HYP - 1].speed,
+                   &_loc[CELL_MAX_HYP - 1].direction,
+                   &_loc[CELL_MAX_HYP - 1].verticalAcc,
+                   &_loc[CELL_MAX_HYP - 1].svUsed) == 17) {
+            if (--a >= 0) {
+                debug_if(_debug_trace_on, "Position found at index %d\n", a);
+
+                memcpy(&_loc[a], &_loc[CELL_MAX_HYP - 1], sizeof(*_loc));
+
+                _loc[a].sensor = (b == 0) ? CELL_LAST : (b == 1) ? CELL_GNSS :
+                                 (b == 2) ? CELL_LOCATE : (b == 3) ? CELL_HYBRID : CELL_LAST;
+                _loc[a].time.tm_mon -= 1;
+                _loc[a].time.tm_wday = 0;
+                _loc[a].time.tm_yday = 0;
+                _loc[a].validData = true;
+                _locRcvPos++;
+            }
+        //+UULOC: <sol>,<num>,<sensor_used>,<date>,<time>,<lat>,<long>,<alt>,<lat50>,<long50>,<major50>,<minor50>,<orientation50>,<confidence50>[,<lat95>,<long95>,<major95>,<minor95>,<orientation95>,<confidence95>]
+        } else if (sscanf(urcBuf, ": %d,%d,%d,%d/%d/%d,%d:%d:%d.%*d,%f,%f,%d,%*f,%*f,%d,%*d,%*d,%*d",
+                   &a, &_locExpPos, &b,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_mday,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_mon,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_year,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_hour,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_min,
+                   &_loc[CELL_MAX_HYP - 1].time.tm_sec,
+                   &_loc[CELL_MAX_HYP - 1].latitude,
+                   &_loc[CELL_MAX_HYP - 1].longitude,
+                   &_loc[CELL_MAX_HYP - 1].altitude,
+                   &_loc[CELL_MAX_HYP - 1].uncertainty) == 13) {
+            if (--a >= 0) {
+
+                debug_if(_debug_trace_on, "Position found at index %d\n", a);
+
+                memcpy(&_loc[a], &_loc[CELL_MAX_HYP - 1], sizeof(*_loc));
+
+                _loc[a].sensor = (b == 0) ? CELL_LAST : (b == 1) ? CELL_GNSS :
+                                 (b == 2) ? CELL_LOCATE : (b == 3) ? CELL_HYBRID : CELL_LAST;
+                _loc[a].time.tm_mon -= 1;
+                _loc[a].time.tm_wday = 0;
+                _loc[a].time.tm_yday = 0;
+                _loc[a].validData = true;
+                _locRcvPos++;
+            }
+        }
+    }
+}
+
+/**********************************************************************
+ * PUBLIC METHODS: GENERAL
+ **********************************************************************/
+
+// Constructor.
+UbloxATCellularInterfaceExt::UbloxATCellularInterfaceExt(PinName tx,
+                                                         PinName rx,
+                                                         int baud,
+                                                         bool debugOn):
+                             UbloxATCellularInterface(tx, rx, baud, debugOn)
+{
+    // Zero HTTP stuff
+    memset(_httpProfiles, 0, sizeof(_httpProfiles));
+    for (unsigned int profile = 0; profile < sizeof(_httpProfiles) / sizeof(_httpProfiles[0]);
+         profile++) {
+        _httpProfiles[profile].modemHandle = HTTP_PROF_UNUSED;
+    }
+
+    // Zero FTP stuff
+    _ftpTimeout = TIMEOUT_BLOCKING;
+    _lastFtpOpCodeResult = FTP_OP_CODE_UNUSED;
+    _lastFtpResult = 0;
+    _lastFtpOpCodeData = FTP_OP_CODE_UNUSED;
+    _ftpBuf = NULL;
+    _ftpBufLen = 0;
+    _ftpError.eClass = 0;
+    _ftpError.eCode = 0;
+
+    // Zero Cell Locate stuff
+    _locRcvPos = 0;
+    _locExpPos = 0;
+
+    // URC handler for HTTP
+    _at->oob("+UUHTTPCR", callback(this, &UbloxATCellularInterfaceExt::UUHTTPCR_URC));
+
+    // URC handlers for FTP
+    _at->oob("+UUFTPCR", callback(this, &UbloxATCellularInterfaceExt::UUFTPCR_URC));
+    _at->oob("+UUFTPCD", callback(this, &UbloxATCellularInterfaceExt::UUFTPCD_URC));
+
+    // URC handlers for Cell Locate
+    _at->oob("+UULOCIND", callback(this, &UbloxATCellularInterfaceExt::UULOCIND_URC));
+    _at->oob("+UULOC", callback(this, &UbloxATCellularInterfaceExt::UULOC_URC));
+}
+
+// Destructor.
+UbloxATCellularInterfaceExt::~UbloxATCellularInterfaceExt()
+{
+}
+
+/**********************************************************************
+ * PUBLIC METHODS: HTTP
+ **********************************************************************/
+
+// Find a free profile.
+int UbloxATCellularInterfaceExt::httpAllocProfile()
+{
+    int profile = HTTP_PROF_UNUSED;
+    LOCK();
+
+    // Find a free HTTP profile
+    profile = findProfile();
+    debug_if(_debug_trace_on, "httpFindProfile: profile is %d\n", profile);
+
+    if (profile != HTTP_PROF_UNUSED) {
+        _httpProfiles[profile].modemHandle = 1;
+        _httpProfiles[profile].timeout     = TIMEOUT_BLOCKING;
+        _httpProfiles[profile].pending     = false;
+        _httpProfiles[profile].cmd         = -1;
+        _httpProfiles[profile].result      = -1;
+    }
+
+    UNLOCK();
+    return profile;
+}
+
+// Free a profile.
+bool UbloxATCellularInterfaceExt::httpFreeProfile(int profile)
+{
+    bool success = false;
+    LOCK();
+
+    if (IS_PROFILE(profile)) {
+        debug_if(_debug_trace_on, "httpFreeProfile(%d)\n", profile);
+        _httpProfiles[profile].modemHandle = HTTP_PROF_UNUSED;
+        _httpProfiles[profile].timeout     = TIMEOUT_BLOCKING;
+        _httpProfiles[profile].pending     = false;
+        _httpProfiles[profile].cmd         = -1;
+        _httpProfiles[profile].result      = -1;
+        success = _at->send("AT+UHTTP=%d", profile) && _at->recv("OK");
+    }
+
+    UNLOCK();
+    return success;
+}
+
+// Set the blocking/timeout state of a profile.
+bool UbloxATCellularInterfaceExt::httpSetTimeout(int profile, int timeout)
+{
+    bool success = false;
+    LOCK();
+
+    debug_if(_debug_trace_on, "httpSetTimeout(%d, %d)\n", profile, timeout);
+
+    if (IS_PROFILE(profile)) {
+        _httpProfiles[profile].timeout = timeout;
+        success = true;
+    }
+
+    UNLOCK();
+    return success;
+}
+
+// Set a profile back to defaults.
+bool UbloxATCellularInterfaceExt::httpResetProfile(int httpProfile)
+{
+    bool success = false;
+    LOCK();
+
+    debug_if(_debug_trace_on, "httpResetProfile(%d)\n", httpProfile);
+    success = _at->send("AT+UHTTP=%d", httpProfile) && _at->recv("OK");
+
+    UNLOCK();
+    return success;
+}
+
+// Set HTTP parameters.
+bool UbloxATCellularInterfaceExt::httpSetPar(int httpProfile,
+                                             HttpOpCode httpOpCode,
+                                             const char * httpInPar)
+{
+    bool success = false;
+    int httpInParNum = 0;
+    SocketAddress address;
+
+    debug_if(_debug_trace_on, "httpSetPar(%d, %d, \"%s\")\n", httpProfile, httpOpCode, httpInPar);
+    if (IS_PROFILE(httpProfile)) {
+        LOCK();
+
+        switch(httpOpCode) {
+            case HTTP_IP_ADDRESS:   // 0
+                if (gethostbyname(httpInPar, &address) == NSAPI_ERROR_OK) {
+                    success = _at->send("AT+UHTTP=%d,%d,\"%s\"",
+                                        httpProfile, httpOpCode, address.get_ip_address()) &&
+                              _at->recv("OK");
+                }
+                break;
+            case HTTP_SERVER_NAME:  // 1
+            case HTTP_USER_NAME:    // 2
+            case HTTP_PASSWORD:     // 3
+                success = _at->send("AT+UHTTP=%d,%d,\"%s\"", httpProfile, httpOpCode, httpInPar) &&
+                          _at->recv("OK");
+                break;
+
+            case HTTP_AUTH_TYPE:    // 4
+            case HTTP_SERVER_PORT:  // 5
+                httpInParNum = atoi(httpInPar);
+                success = _at->send("AT+UHTTP=%d,%d,%d", httpProfile, httpOpCode, httpInParNum) &&
+                          _at->recv("OK");
+                break;
+
+            case HTTP_SECURE:       // 6
+                httpInParNum = atoi(httpInPar);
+                success = _at->send("AT+UHTTP=%d,%d,%d", httpProfile, httpOpCode, httpInParNum) &&
+                          _at->recv("OK");
+                break;
+
+            default:
+                debug_if(_debug_trace_on, "httpSetPar: unknown httpOpCode %d\n", httpOpCode);
+                break;
+        }
+
+        UNLOCK();
+    }
+
+    return success;
+}
+
+// Perform an HTTP command.
+UbloxATCellularInterfaceExt::Error * UbloxATCellularInterfaceExt::httpCommand(int httpProfile,
+                                                                              HttpCmd httpCmd,
+                                                                              const char *httpPath,
+                                                                              const char *rspFile,
+                                                                              const char *sendStr,
+                                                                              int httpContentType,
+                                                                              const char *httpCustomPar,
+                                                                              char *buf, int len)
+{
+    bool atSuccess = false;
+    bool success = false;
+    int at_timeout;
+    char defaultFilename[] = "http_last_response_x";
+
+    debug_if(_debug_trace_on, "%s\n", getHttpCmd(httpCmd));
+
+    if (IS_PROFILE(httpProfile)) {
+        LOCK();
+        at_timeout = _at_timeout; // Has to be inside LOCK()s
+
+        if (rspFile == NULL) {
+            sprintf(defaultFilename + sizeof (defaultFilename) - 2, "%1d", httpProfile);
+            rspFile = defaultFilename;
+        }
+
+        switch (httpCmd) {
+            case HTTP_HEAD:
+                atSuccess = _at->send("AT+UHTTPC=%d,%d,\"%s\",\"%s\"", httpProfile, httpCmd,
+                                      httpPath, rspFile) &&
+                            _at->recv("OK");
+                break;
+            case HTTP_GET:
+                atSuccess = _at->send("AT+UHTTPC=%d,%d,\"%s\",\"%s\"", httpProfile, httpCmd,
+                                      httpPath, rspFile) &&
+                            _at->recv("OK");
+                break;
+            case HTTP_DELETE:
+                atSuccess = _at->send("AT+UHTTPC=%d,%d,\"%s\",\"%s\"", httpProfile, httpCmd,
+                                      httpPath, rspFile) &&
+                            _at->recv("OK");
+                break;
+            case HTTP_PUT:
+                // In this case the parameter sendStr is a filename
+                atSuccess = _at->send("AT+UHTTPC=%d,%d,\"%s\",\"%s\",\"%s\"", httpProfile, httpCmd,
+                                      httpPath, rspFile, sendStr) &&
+                            _at->recv("OK");
+                break;
+            case HTTP_POST_FILE:
+                // In this case the parameter sendStr is a filename
+                if (httpContentType != 6) {
+                    atSuccess = _at->send("AT+UHTTPC=%d,%d,\"%s\",\"%s\",\"%s\",%d",
+                                          httpProfile, httpCmd, httpPath, rspFile, sendStr,
+                                          httpContentType) &&
+                                _at->recv("OK");
+                } else {
+                    atSuccess = _at->send("AT+UHTTPC=%d,%d,\"%s\",\"%s\",\"%s\",%d,%s",
+                                          httpProfile, httpCmd, httpPath, rspFile, sendStr,
+                                          httpContentType,
+                                          httpCustomPar) &&
+                                _at->recv("OK");
+                }
+                break;
+            case HTTP_POST_DATA:
+                // In this case the parameter sendStr is a string containing data
+                if (httpContentType != 6) {
+                    atSuccess = _at->send("AT+UHTTPC=%d,%d,\"%s\",\"%s\",\"%s\",%d",
+                                          httpProfile, httpCmd, httpPath, rspFile, sendStr,
+                                          httpContentType) &&
+                                _at->recv("OK");
+                } else {
+                    atSuccess = _at->send("AT+UHTTPC=%d,%d,\"%s\",\"%s\",\"%s\",%d,%s",
+                                          httpProfile, httpCmd, httpPath, rspFile, sendStr,
+                                          httpContentType,
+                                          httpCustomPar) &&
+                                _at->recv("OK");
+                }
+                break;
+            default:
+                debug_if(_debug_trace_on, "HTTP command not recognised\n");
+                break;
+        }
+
+        if (atSuccess) {
+            Timer timer;
+
+            at_set_timeout(1000);
+            _httpProfiles[httpProfile].pending = true;
+            _httpProfiles[httpProfile].result = -1;
+
+            // Waiting for unsolicited result code
+            timer.start();
+            while (_httpProfiles[httpProfile].pending) {
+                if (_httpProfiles[httpProfile].result != -1) {
+                    // Received unsolicited: starting its analysis
+                    _httpProfiles[httpProfile].pending = false;
+                    if (_httpProfiles[httpProfile].result == 1) {
+                        // HTTP command successfully executed
+                        if (readFile(rspFile, buf, len) >= 0) {
+                            success = true;
+                        }
+                    } else {
+                        // Retrieve the error class and code
+                        if (_at->send("AT+UHTTPER=%d", httpProfile) &&
+                            _at->recv("AT+UHTTPER=%*d,%d,%d",
+                                      &(_httpProfiles[httpProfile].httpError.eClass),
+                                      &(_httpProfiles[httpProfile].httpError.eCode)) &&
+                            _at->recv("OK")) {
+                            debug_if(_debug_trace_on, "HTTP error class %d, code %d\n",
+                                    _httpProfiles[httpProfile].httpError.eClass,
+                                    _httpProfiles[httpProfile].httpError.eCode);
+                        }
+                    }
+                } else if (!TIMEOUT(timer, _httpProfiles[httpProfile].timeout)) {
+                    // Wait for URCs
+                    _at->recv(UNNATURAL_STRING);
+                } else  {
+                    _httpProfiles[httpProfile].pending = false;
+                }
+            }
+            timer.stop();
+
+            at_set_timeout(at_timeout);
+
+            if (!success) {
+                debug_if(_debug_trace_on, "%s: ERROR\n", getHttpCmd(httpCmd));
+            }
+
+        }
+
+        UNLOCK();
+    }
+
+    return success ? NULL : &(_httpProfiles[httpProfile].httpError);
+}
+
+/**********************************************************************
+ * PUBLIC METHODS: FTP
+ **********************************************************************/
+
+// Set the blocking/timeout for FTP.
+bool UbloxATCellularInterfaceExt::ftpSetTimeout(int timeout)
+{
+    LOCK();
+    debug_if(_debug_trace_on, "ftpSetTimeout(%d)\n", timeout);
+    _ftpTimeout = timeout;
+    UNLOCK();
+
+    return true;
+}
+
+// Reset the FTP configuration back to defaults.
+bool UbloxATCellularInterfaceExt::ftpResetPar()
+{
+    bool success = true;
+    LOCK();
+
+    debug_if(_debug_trace_on, "ftpResetPar()\n");
+    for (int x = 0; success && (x < NUM_FTP_OP_CODES); x++) {
+        success = _at->send("AT+UFTP=%d", x) && _at->recv("OK");
+    }
+
+    UNLOCK();
+    return success;
+}
+
+// Set FTP parameters.
+bool UbloxATCellularInterfaceExt::ftpSetPar(FtpOpCode ftpOpCode,
+                                            const char * ftpInPar)
+{
+    bool success = false;
+    int ftpInParNum = 0;
+    LOCK();
+
+    debug_if(_debug_trace_on, "ftpSetPar(%d, %s)\n", ftpOpCode, ftpInPar);
+    switch (ftpOpCode) {
+        case FTP_IP_ADDRESS:         // 0
+        case FTP_SERVER_NAME:        // 1
+        case FTP_USER_NAME:          // 2
+        case FTP_PASSWORD:           // 3
+        case FTP_ACCOUNT:            // 4
+            success = _at->send("AT+UFTP=%d,\"%s\"", ftpOpCode, ftpInPar) &&
+                      _at->recv("OK");
+            break;
+        case FTP_INACTIVITY_TIMEOUT: // 5
+        case FTP_MODE:               // 6
+        case FTP_SERVER_PORT:        // 7
+        case FTP_SECURE:             // 8
+            ftpInParNum = atoi(ftpInPar);
+            success = _at->send("AT+UFTP=%d,%d", ftpOpCode, ftpInParNum) &&
+                      _at->recv("OK");
+            break;
+        default:
+            debug_if(_debug_trace_on, "ftpSetPar: unknown ftpOpCode %d\n", ftpOpCode);
+            break;
+    }
+
+    UNLOCK();
+    return success;
+}
+
+// Perform an FTP command.
+UbloxATCellularInterfaceExt::Error * UbloxATCellularInterfaceExt::ftpCommand(FtpCmd ftpCmd,
+                                                                             const char* file1,
+                                                                             const char* file2,
+                                                                             int offset,
+                                                                             char* buf, int len)
+{
+    bool atSuccess = false;
+    bool success = false;
+    int at_timeout;
+    LOCK();
+    at_timeout = _at_timeout; // Has to be inside LOCK()s
+
+    debug_if(_debug_trace_on, "%s\n", getFtpCmd(ftpCmd));
+    switch (ftpCmd) {
+        case FTP_LOGOUT:
+        case FTP_LOGIN:
+            atSuccess = _at->send("AT+UFTPC=%d", ftpCmd) && _at->recv("OK");
+            break;
+        case FTP_DELETE_FILE:
+        case FTP_CD:
+        case FTP_MKDIR:
+        case FTP_RMDIR:
+        case FTP_FOTA_FILE:
+            atSuccess = _at->send("AT+UFTPC=%d,\"%s\"", ftpCmd, file1) &&
+                        _at->recv("OK");
+            break;
+        case FTP_RENAME_FILE:
+            atSuccess = _at->send("AT+UFTPC=%d,\"%s\",\"%s\"",
+                                  ftpCmd, file1, file2) &&
+                        _at->recv("OK");
+            break;
+        case FTP_GET_FILE:
+        case FTP_PUT_FILE:
+            if (file2 == NULL) {
+                file2 = file1;
+            }
+            atSuccess = _at->send("AT+UFTPC=%d,\"%s\",\"%s\",%d",
+                                  ftpCmd, file1, file2, offset) &&
+                        _at->recv("OK");
+            break;
+        case FTP_FILE_INFO:
+        case FTP_LS:
+            _ftpBuf = buf;
+            _ftpBufLen = len;
+            // Add a terminator in case nothing comes back
+            if (_ftpBufLen > 0) {
+                *_ftpBuf = 0;
+            }
+            if (file1 == NULL) {
+                atSuccess = _at->send("AT+UFTPC=%d", ftpCmd) &&
+                            _at->recv("OK");
+            } else {
+                atSuccess = _at->send("AT+UFTPC=%d,\"%s\"", ftpCmd, file1) &&
+                            _at->recv("OK");
+            }
+            break;
+        default:
+            debug_if(_debug_trace_on, "FTP command not recognised/supported\n");
+            break;
+    }
+
+    // Wait for the result to arrive back
+    if (atSuccess) {
+        Timer timer;
+
+        at_set_timeout(1000);
+        _lastFtpOpCodeData = FTP_OP_CODE_UNUSED;
+        _lastFtpOpCodeResult = FTP_OP_CODE_UNUSED;
+        _lastFtpResult = -1; // just for safety
+        // Waiting for result to arrive
+        timer.start();
+        while ((_lastFtpOpCodeResult == FTP_OP_CODE_UNUSED) &&
+                !TIMEOUT(timer, _ftpTimeout)) {
+            _at->recv(UNNATURAL_STRING);
+        }
+        timer.stop();
+
+        if ((_lastFtpOpCodeResult == ftpCmd) && (_lastFtpResult == 1)) {
+            // Got a result for our FTP op code and it is good
+            success = true;
+        } else {
+            // Retrieve the error class and code
+            if (_at->send("AT+UFTPER") &&
+                _at->recv("+UFTPER:%d,%d", &(_ftpError.eClass), &(_ftpError.eCode)) &&
+                _at->recv("OK")) {
+                debug_if(_debug_trace_on, "FTP Error class %d, code %d\n",
+                         _ftpError.eClass, _ftpError.eCode);
+            }
+        }
+
+        at_set_timeout(at_timeout);
+
+        if (!success) {
+            debug_if(_debug_trace_on, "%s: ERROR\n", getFtpCmd(ftpCmd));
+        }
+    }
+
+    // Set these back to nothing to stop the URC splatting
+    _ftpBuf = NULL;
+    _ftpBufLen = 0;
+
+    UNLOCK();
+    return success ? NULL : &_ftpError;
+}
+
+/**********************************************************************
+ * PUBLIC METHODS: Cell Locate
+ **********************************************************************/
+
+// Configure CellLocate TCP Aiding server.
+bool UbloxATCellularInterfaceExt::cellLocSrvTcp(const char* token,
+                                                const char* server_1,
+                                                const char* server_2,
+                                                int days, int period,
+                                                int resolution)
+{
+    bool success = false;
+    LOCK();
+
+    if ((_dev_info.dev == DEV_LISA_U2_03S) || (_dev_info.dev  == DEV_SARA_U2)){
+        success = _at->send("AT+UGSRV=\"%s\",\"%s\",\"%s\",%d,%d,%d",
+                            server_1, server_2, token, days, period, resolution) &&
+                  _at->recv("OK");
+    }
+
+    UNLOCK();
+    return success;
+}
+
+// Configure CellLocate UDP Aiding server.
+bool UbloxATCellularInterfaceExt::cellLocSrvUdp(const char* server_1,
+                                                int port, int latency,
+                                                int mode)
+{
+
+    bool success = false;
+    LOCK();
+
+    if (_dev_info.dev != DEV_TOBY_L2) {
+        success = _at->send("AT+UGAOP=\"%s\",%d,%d,%d", server_1, port, latency, mode) &&
+                  _at->recv("OK");
+    }
+
+    UNLOCK();
+    return success;
+}
+
+// Configure Cell Locate location sensor.
+bool UbloxATCellularInterfaceExt::cellLocConfig(int scanMode)
+{
+    bool success;
+    LOCK();
+
+    success = _at->send("AT+ULOCCELL=%d", scanMode) &&
+              _at->recv("OK");
+
+    UNLOCK();
+    return success;
+}
+
+// Request CellLocate.
+bool UbloxATCellularInterfaceExt::cellLocRequest(CellSensType sensor,
+                                                 int timeout,
+                                                 int accuracy,
+                                                 CellRespType type,
+                                                 int hypothesis)
+{
+    bool success = false;
+
+    if ((hypothesis <= CELL_MAX_HYP) &&
+        !((hypothesis > 1) && (type != CELL_MULTIHYP))) {
+
+        LOCK();
+
+        _locRcvPos = 0;
+        _locExpPos = 0;
+
+        for (int i = 0; i < hypothesis; i++) {
+            _loc[i].validData = false;
+        }
+
+        // Switch on the URC
+        if (_at->send("AT+ULOCIND=1") && _at->recv("OK")) {
+            // Switch on Cell Locate
+            success = _at->send("AT+ULOC=2,%d,%d,%d,%d,%d", sensor, type, timeout, accuracy, hypothesis) &&
+                      _at->recv("OK");
+            // Answers are picked up by the URC
+        }
+
+        UNLOCK();
+    }
+
+    return success;
+}
+
+// Get a position record.
+bool UbloxATCellularInterfaceExt::cellLocGetData(CellLocData *data, int index)
+{
+    bool success = false;
+
+    if (_loc[index].validData) {
+        LOCK();
+        memcpy(data, &_loc[index], sizeof(*_loc));
+        success = true;
+        UNLOCK();
+    }
+
+    return success;
+}
+
+// Get number of position records received.
+int UbloxATCellularInterfaceExt::cellLocGetRes()
+{
+    int at_timeout;
+    LOCK();
+
+    at_timeout = _at_timeout; // Has to be inside LOCK()s
+    at_set_timeout(1000);
+    // Wait for URCs
+    _at->recv(UNNATURAL_STRING);
+    at_set_timeout(at_timeout);
+
+    UNLOCK();
+    return _locRcvPos;
+}
+
+// Get number of positions records expected to be received.
+int UbloxATCellularInterfaceExt::cellLocGetExpRes()
+{
+    int numRecords = 0;
+    LOCK();
+
+    _at->recv("OK");
+
+    if (_locRcvPos > 0) {
+        numRecords = _locExpPos;
+    }
+
+    UNLOCK();
+    return numRecords;
+}
+
+// End of file
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UbloxATCellularInterfaceExt.h	Mon Jun 05 12:58:04 2017 +0000
@@ -0,0 +1,605 @@
+/* Copyright (c) 2017 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UBLOX_AT_CELLULAR_INTERFACE_EXT_
+#define _UBLOX_AT_CELLULAR_INTERFACE_EXT_
+
+#include "ublox_modem_driver/UbloxAtCellularInterface.h"
+#include "UbloxCellularDriverGen.h"
+
+/**UbloxATCellularInterfaceExt class.
+ *
+ * This interface extends the UbloxATCellularInterface to
+ * include other features that use the IP stack on board the
+ * cellular modem: HTTP, FTP and Cell Locate.
+ *
+ * Note: the UbloxCellularGeneric class is required because
+ * reading a large HTTP response is performed via a modem
+ * file system call and the UbloxCellularGeneric class is
+ * where modem file system support is provided.
+ */
+class UbloxATCellularInterfaceExt : public  UbloxATCellularInterface, public UbloxCellularDriverGen {
+
+public:
+    /** Constructor.
+     *
+     * @param tx       the UART TX data pin to which the modem is attached.
+     * @param rx       the UART RX data pin to which the modem is attached.
+     * @param baud     the UART baud rate.
+     * @param debugOn  true to switch AT interface debug on, otherwise false.
+     */
+    UbloxATCellularInterfaceExt(PinName tx = MDMTXD,
+                                PinName rx = MDMRXD,
+                                int baud = MBED_CONF_UBLOX_CELL_BAUD_RATE,
+                                bool debugOn = false);
+
+     /* Destructor.
+      */
+     virtual ~UbloxATCellularInterfaceExt();
+
+    /**********************************************************************
+     * PUBLIC: General
+     **********************************************************************/
+
+    /** Infinite timeout.
+     */
+    #define TIMEOUT_BLOCKING -1
+
+    /** A struct containing an HTTP or FTP error class and code
+     */
+     typedef struct {
+        int eClass;
+        int eCode;
+    } Error;
+
+    /**********************************************************************
+     * PUBLIC: HTTP
+     **********************************************************************/
+
+    /** HTTP profile unused marker.
+     */
+    #define HTTP_PROF_UNUSED -1
+
+    /** HTTP configuration parameters (reference to HTTP control +UHTTP).
+     */
+    typedef enum {
+        HTTP_IP_ADDRESS = 0,
+        HTTP_SERVER_NAME = 1,
+        HTTP_USER_NAME = 2,
+        HTTP_PASSWORD = 3,
+        HTTP_AUTH_TYPE = 4,
+        HTTP_SERVER_PORT = 5,
+        HTTP_SECURE = 6
+    } HttpOpCode;
+
+    /** Type of HTTP Command.
+     */
+    typedef enum {
+        HTTP_HEAD = 0,
+        HTTP_GET = 1,
+        HTTP_DELETE = 2,
+        HTTP_PUT = 3,
+        HTTP_POST_FILE = 4,
+        HTTP_POST_DATA = 5
+    } HttpCmd;
+
+    /** HTTP content types.
+     */
+    typedef enum {
+        HTTP_CONTENT_URLENCODED = 0,
+        HTTP_CONTENT_TEXT = 1,
+        HTTP_CONTENT_OCTET_STREAM = 2,
+        HTTP_CONTENT_FORM_DATA = 3,
+        HTTP_CONTENT_JSON = 4,
+        HTTP_CONTENT_XML = 5,
+        HTTP_CONTENT_USER_DEFINED = 6
+    } HttpContentType;
+
+    /** Find a free HTTP profile.
+     *
+     * A profile will be blocking when first allocated.
+     *
+     * @return the profile or negative if none are available.
+     */
+    int httpAllocProfile();
+    
+    /** Free the HTTP profile.
+     *
+     * @param profile the HTTP profile handle.
+     * @return           true if successful, otherwise false.
+     */
+    bool httpFreeProfile(int profile);
+
+    /** Set the timeout for this profile.
+     *
+     * @param profile    the HTTP profile handle.
+     * @param timeout    -1 blocking, else non-blocking timeout in milliseconds.
+     * @return           true if successful, otherwise false.
+     */
+    bool httpSetTimeout(int profile, int timeout);
+    
+    /** Reset a HTTP profile back to defaults.
+     *
+     * This may be called if the state of a HTTP profile
+     * during parameter setting or exchange of HTTP commands
+     * has become confusing/unknown.
+     *
+     * @param httpProfile the HTTP profile to be reset.
+     * @return            true if successful, false otherwise.
+     */
+    bool httpResetProfile(int httpProfile);
+    
+    /** Set HTTP parameters.
+     *
+     * This should be called as many times as is necessary
+     * to set all the possible parameters (HttpOpCode).
+     *
+     * See section 28.1 of u-blox-ATCommands_Manual(UBX-13002752).pdf
+     * for full details.  By example:
+     *
+     * httpOpCode          httpInPar
+     * HTTP_IP_ADDRESS     "145.33.18.10" (the target HTTP server IP address)
+     * HTTP_SERVER_NAME    "www.myhttpserver.com" (the target HTTP server name)
+     * HTTP_USER_NAME      "my_username"
+     * HTTP_PASSWORD       "my_password"
+     * HTTP_AUTH_TYPE      "0" for no authentication, "1" for username/password
+     *                     authentication (the default is 0)
+     * HTTP_SERVER_PORT    "81" (default is port 80)
+     * HTTP_SECURE         "0" for no security, "1" for TLS (the default is 0)
+     *
+     * @param httpProfile the HTTP profile identifier.
+     * @param httpOpCode  the HTTP operation code.
+     * @param httpInPar   the HTTP input parameter.
+     * @return            true if successful, false otherwise.
+     */
+    bool httpSetPar(int httpProfile, HttpOpCode httpOpCode, const char * httpInPar);
+    
+    /** Perform a HTTP command.
+     *
+     * See section 28.3 of u-blox-ATCommands_Manual(UBX-13002752).pdf
+     * for full details.  By example, it works like this:
+     *
+     * httpCmd        httpPath       rspFile     sendStr  httpContentType httpCustomPar
+     * HEAD       "path/file.html"     NULL       NULL         0             NULL
+     * GET        "path/file.html"     NULL       NULL         0             NULL
+     * DELETE     "path/file.html"     NULL       NULL         0             NULL
+     * PUT        "path/file.html"     NULL    "myfile.txt"  0 to 6         Note 1
+     * POST_FILE  "path/file.html"     NULL    "myfile.txt"  0 to 6         Note 1
+     * POST       "path/file.html"     NULL   "hello there!" 0 to 6         Note 1
+     *
+     * Note 1: httpCustomPar is only applicable when httpContentType = HTTP_CONTENT_USER_DEFINED.
+     *
+     * The server to which this command is directed must have previously been
+     * set with a call to httpSetPar().  If the server requires TLS (i.e. "HTTPS"),
+     * then set that up with httpSetPar() also (HTTP_SECURE).
+     *
+     * rspFile may be left as NULL as the server response will be returned in buf.
+     * Alternatively, a rspFile may be given (e.g. "myresponse.txt") and this can
+     * later be read from the modem file system using readFile().
+     *
+     * @param httpProfile     the HTTP profile identifier.
+     * @param httpCmd         the HTTP command.
+     * @param httpPath        the path of resource on the HTTP server.
+     * @param rspFile         the local modem file where the server
+     *                        response will be stored, use NULL for
+     *                        don't care.
+     * @param sendStr         the filename or string to be sent
+     *                        to the HTTP server with the command request.
+     * @param httpContentType the HTTP Content-Type identifier.
+     * @param httpCustomPar   the parameter for a user defined HTTP Content-Type.
+     * @param buf             the buffer to read into.
+     * @param len             the size of the buffer to read into.
+     * @return                NULL if successful, otherwise a pointer to
+     *                        a Error struct containing the error class and error
+     *                        code, see section Appendix A.B of
+     *                        u-blox-ATCommands_Manual(UBX-13002752).pdf for details.
+     */
+    Error * httpCommand(int httpProfile, HttpCmd httpCmd, const char* httpPath,
+                        const char* rspFile, const char* sendStr,
+                        int httpContentType, const char* httpCustomPar,
+                        char* buf, int len);
+
+    /**********************************************************************
+     * PUBLIC: FTP
+     **********************************************************************/
+
+    /** FTP configuration parameters (reference to FTP control +UFTP).
+     */
+    typedef enum {
+        FTP_IP_ADDRESS = 0,
+        FTP_SERVER_NAME = 1,
+        FTP_USER_NAME = 2,
+        FTP_PASSWORD = 3,
+        FTP_ACCOUNT = 4,
+        FTP_INACTIVITY_TIMEOUT = 5,
+        FTP_MODE = 6,
+        FTP_SERVER_PORT = 7,
+        FTP_SECURE = 8,
+        NUM_FTP_OP_CODES
+    } FtpOpCode;
+
+    /** Type of FTP Command.
+     */
+    typedef enum {
+        FTP_LOGOUT = 0,
+        FTP_LOGIN = 1,
+        FTP_DELETE_FILE = 2,
+        FTP_RENAME_FILE = 3,
+        FTP_GET_FILE = 4,
+        FTP_PUT_FILE = 5,
+        FTP_GET_DIRECT = 6,
+        FTP_PUT_DIRECT = 7,
+        FTP_CD = 8,
+        FTP_MKDIR = 10,
+        FTP_RMDIR = 11,
+        FTP_FILE_INFO = 13,
+        FTP_LS = 14,
+        FTP_FOTA_FILE = 100
+    } FtpCmd;
+
+    /** Set the timeout for FTP operations.
+     *
+     * @param timeout -1 blocking, else non-blocking timeout in milliseconds.
+     * @return         true if successful, otherwise false.
+     */
+    bool ftpSetTimeout(int timeout);
+
+    /** Reset the FTP configuration back to defaults.
+     *
+     * @return   true if successful, false otherwise.
+     */
+    bool ftpResetPar();
+    
+    /** Set FTP parameters.
+     *
+     * This should be called as many times as is necessary
+     * to set all the possible parameters (FtpOpCode).
+     *
+     * See section 27.1 of u-blox-ATCommands_Manual(UBX-13002752).pdf
+     * for full details.  By example:
+     *
+     * ftpOpCode              ftpInPar
+     * FTP_IP_ADDRESS         "145.33.18.10" (the target FTP server IP address)
+     * FTP_SERVER_NAME        "www.ftpserver.com" (the target FTP server name)
+     * FTP_USER_NAME          "my_username"
+     * FTP_PASSWORD           "my_password"
+     * FTP_ACCOUNT            "my_account" (not required by most FTP servers)
+     * FTP_INACTIVITY_TIMEOUT "60" (the default is 0, which means no timeout)
+     * FTP_MODE               "0" for active, "1" for passive (the default is 0) 
+     * FTP_SERVER_PORT        "25" (default is port 21)
+     * FTP_SECURE             "0" for no security, "1" for SFTP (the default is 0)
+     *
+     * @param ftpOpCode  the FTP operation code.
+     * @param ftpInPar   the FTP input parameter.
+     * @return           true if successful, false otherwise.
+     */
+    bool ftpSetPar(FtpOpCode ftpOpCode, const char * ftpInPar);
+    
+    /** Perform an FTP command.
+     *
+     * Connect() must have been called previously to establish a data
+     * connection.
+     *
+     * See section 27.2 of u-blox-ATCommands_Manual(UBX-13002752).pdf
+     * for full details.  By example, it works like this:
+     *
+     * ftpCmd               file1      file2     offset    buf     len
+     * FTP_LOGOUT            N/A        N/A       N/A      N/A     N/A 
+     * FTP_LOGIN             N/A        N/A       N/A      N/A     N/A 
+     * FTP_DELETE_FILE   "the_file"     N/A       N/A      N/A     N/A 
+     * FTP_RENAME_FILE   "old_name"  "new_name"   N/A      N/A     N/A 
+     * FTP_GET_FILE      "the_file"    Note 1  0 - 65535   N/A     N/A (Notes 2)
+     * FTP_PUT_FILE      "the_file"    Note 1  0 - 65535   N/A     N/A (Note 2)
+     * FTP_CD            "dir1\dir2"    N/A       N/A      N/A     N/A 
+     * FTP_MKDIR         "newdir"       N/A       N/A      N/A     N/A 
+     * FTP_RMDIR         "dir"          N/A       N/A      N/A     N/A 
+     * FTP_FILE_INFO     "the_path"     N/A       N/A         Note 3
+     * FTP_LS            "the_path"     N/A       N/A         Note 3
+     * FTP_FOTA_FILE     "the_file"     N/A       N/A         Note 4
+     *
+     * Note 1: for this case, file2 is the name that the file should be
+     * given when it arrives (in the modem file system for a GET, at the FTP
+     * server for a PUT); if set to NULL then file1 is used.
+     * Note 2: the file will placed into the modem file system for the
+     * GET case (and can be read with readFile()), or must already be in the
+     * modem file system, (can be written using writeFile()) for the PUT case.
+     * Note 3: buf should point to the location where the file info
+     * or directory listing is to be stored and len should be the maximum
+     * length that can be stored.
+     * Note 4: a hex string representing the MD5 sum of the FOTA file will be
+     * stored at buf; len must be at least 32 as an MD5 sum is 16 bytes.
+     * FTP_FOTA_FILE is not supported on SARA-U2.
+     * Note 5: FTP_GET_DIRECT and FTP_PUT_DIRECT are not supported by
+     * this driver.
+     *
+     * @param ftpCmd     the FTP command.
+     * @param file1      the first file name if required (NULL otherwise).
+     * @param file2      the second file name if required (NULL otherwise).
+     * @param offset     the offset (in bytes) at which to begin the get
+     *                   or put operation within the file.
+     * @param buf        pointer to a buffer, required for FTP_DIRECT mode
+     *                   and FTP_LS only.
+     * @param len        the size of buf.
+     * @return           NULL if successful, otherwise a pointer to
+     *                   a Error struct containing the error class and error
+     *                   code, see section Appendix A.B of
+     *                   u-blox-ATCommands_Manual(UBX-13002752).pdf for details.
+     */
+    Error *ftpCommand(FtpCmd ftpCmd, const char* file1 = NULL, const char* file2 = NULL,
+                      int offset = 0, char* buf = NULL, int len = 0);
+
+    /**********************************************************************
+     * PUBLIC: Cell Locate
+     **********************************************************************/
+ 
+    /** Which form of Cell Locate sensing to use.
+     */
+    typedef enum {
+        CELL_LAST,
+        CELL_GNSS,
+        CELL_LOCATE,
+        CELL_HYBRID
+    } CellSensType;
+
+    /** Types of Cell Locate response.
+     */
+    typedef enum {
+        CELL_DETAILED = 1,
+        CELL_MULTIHYP = 2
+    } CellRespType;
+
+    /** Cell Locate data.
+     */
+    typedef struct {
+        volatile bool validData;      //!< Flag for indicating if data is valid.
+        volatile struct tm time;      //!< GPS Timestamp.
+        volatile float longitude;     //!< Estimated longitude, in degrees.
+        volatile float latitude;      //!< Estimated latitude, in degrees.
+        volatile int altitude;        //!< Estimated altitude, in meters^2.
+        volatile int uncertainty;     //!< Maximum possible error, in meters.
+        volatile int speed;           //!< Speed over ground m/s^2.
+        volatile int direction;       //!< Course over ground in degrees.
+        volatile int verticalAcc;     //!< Vertical accuracy, in meters^2.
+        volatile CellSensType sensor; //!< Sensor used for last calculation.
+        volatile int svUsed;          //!< number of satellite used.
+    } CellLocData;
+
+    /** Configure the Cell Locate TCP aiding server.
+     *
+     * Connect() must have been called previously to establish
+     * a data connection.
+     *
+     * @param server_1   host name of the primary MGA server.
+     * @param server_2   host name of the secondary MGA server.
+     * @param token      authentication token for MGA server access.
+     * @param days       the number of days into the future the off-line
+     *                   data for the u-blox 7.
+     * @param period     the number of weeks into the future the off-line
+     *                   data for u-blox M8.
+     * @param resolution resolution of off-line data for u-blox M8: 1 every
+     *                   day, 0 every other day.
+     * @return           true if the request is successful, otherwise false.
+     */
+    bool cellLocSrvTcp(const char* token, const char* server_1 = "cell-live1.services.u-blox.com",
+                       const char* server_2 = "cell-live2.services.u-blox.com",
+                       int days = 14, int period = 4, int resolution = 1);
+ 
+    /** Configure Cell Locate UDP aiding server.
+     *
+     * Connect() must have been called previously to establish
+     * a data connection.
+     *
+     * @param server_1   host name of the primary MGA server.
+     * @param port       server port.
+     * @param latency    expected network latency in seconds from 0 to 10000 milliseconds.
+     * @param mode       Assist Now management, mode of operation:
+     *                   0 data downloaded at GNSS power up,
+     *                   1 automatically kept alive, manual download.
+     * @return           true if the request is successful, otherwise false.
+     */
+    bool cellLocSrvUdp(const char* server_1 = "cell-live1.services.u-blox.com",
+                       int port = 46434, int latency = 1000, int mode = 0);
+ 
+    /** Configure Cell Locate location sensor.
+     *
+     * @param scanMode network scan mode: 0 normal, 1 deep scan.
+     * @return         true if the request is successful, otherwise false.
+     */
+    bool cellLocConfig(int scanMode);
+ 
+    /** Request a one-shot Cell Locate.
+     *
+     * This function is non-blocking, the result is retrieved using cellLocGetxxx.
+     *
+     * Note: during the location process, unsolicited result codes will be returned
+     * by the modem indicating progress and potentially flagging interesting errors.
+     * In order to see these errors, instantiate this class with debugOn set to true.
+     *   
+     * @param sensor     sensor selection.
+     * @param timeout    timeout period in seconds (1 - 999).
+     * @param accuracy   target accuracy in meters (1 - 999999).
+     * @param type       detailed or multi-hypothesis.
+     * @param hypothesis maximum desired number of responses from CellLocate (up to 16),
+     *                   must be 1 if type is CELL_DETAILED.
+     * @return           true if the request is successful, otherwise false.
+     */
+    bool cellLocRequest(CellSensType sensor, int timeout, int accuracy,
+                        CellRespType type = CELL_DETAILED, int hypothesis = 1);
+
+    /** Get a position record.
+     *
+     * @param data  pointer to a CellLocData structure where the location will be put.
+     * @param index of the position to retrieve.
+     * @return      true if data has been retrieved and copied, false otherwise.
+     */
+    bool cellLocGetData(CellLocData *data, int index = 0);
+    
+    /** Get the number of position records received.
+     *
+     * @return number of position records received.
+     */
+    int cellLocGetRes();
+    
+    /** Get the number of position records expected to be received.
+     *
+     * @return number of position records expected to be received.
+     */
+    int cellLocGetExpRes();
+    
+protected:
+
+    /**********************************************************************
+     * PROTECTED: HTTP
+     **********************************************************************/
+
+    /** Check for timeout.
+     */
+    #define TIMEOUT(t, ms)  ((ms != TIMEOUT_BLOCKING) && (ms < t.read_ms()))
+
+    /** Check for a valid profile.
+     */
+    #define IS_PROFILE(p) (((p) >= 0) && (((unsigned int) p) < (sizeof(_httpProfiles)/sizeof(_httpProfiles[0]))) \
+                           && (_httpProfiles[p].modemHandle != HTTP_PROF_UNUSED))
+
+    /** Management structure for HTTP profiles.
+     *
+     * It is possible to have up to 4 different HTTP profiles (LISA-C200, LISA-U200 and SARA-G350) having:
+     *
+     * @param handle     the current HTTP profile is in handling state or not (default value is HTTP_ERROR).
+     * @param timeout    the timeout for the current HTTP command.
+     * @param pending    the status for the current HTTP command (in processing state or not).
+     * @param cmd        the code for the current HTTP command.
+     * @param result     the result for the current HTTP command once processed.
+     */
+     typedef struct {
+         int modemHandle;
+         int timeout;
+         volatile bool pending;
+         volatile int cmd;
+         volatile int result;
+         Error httpError;
+     } HttpProfCtrl;
+
+    /** The HTTP profile storage.
+     */
+    HttpProfCtrl _httpProfiles[4];
+
+    /** Callback to capture the response to an HTTP command.
+     */
+    void UUHTTPCR_URC();
+
+    /** Find a profile with a given handle.  If no handle is given, find the next
+     * free profile.
+     *
+     * @param modemHandle the handle of the profile to find.
+     * @return            the profile handle or negative if not found/created.
+     */
+    int findProfile(int modemHandle = HTTP_PROF_UNUSED);
+
+    /** Helper function to get a HTTP command as a text string, useful
+     * for debug purposes.
+     *
+     * @param httpCmdCode the HTTP command.
+     * @return            HTTP command in string format.
+     */
+    const char* getHttpCmd(HttpCmd httpCmd);
+
+    /**********************************************************************
+     * PROTECTED: FTP
+     **********************************************************************/
+
+    /** Unused FTP op code marker.
+     */
+    #define FTP_OP_CODE_UNUSED -1
+
+    /** The FTP timeout in milliseconds.
+     */
+    int _ftpTimeout;
+
+    /** A place to store the FTP op code for the last result.
+     */
+    volatile int _lastFtpOpCodeResult;
+
+    /** A place to store the last FTP result.
+     */
+    volatile int _lastFtpResult;
+
+    /** A place to store the last FTP op code for data response.
+     */
+    volatile int _lastFtpOpCodeData;
+
+    /** A place to store data returns from an FTP operation.
+     */
+    char * _ftpBuf;
+
+    /** The length of FTP data that can be stored (at _ftpBuf).
+     */
+    int _ftpBufLen;
+
+    /** storage for the last FTP error
+     */
+    Error _ftpError;
+
+    /** Callback to capture the result of an FTP command.
+     */
+    void UUFTPCR_URC();
+
+    /** Callback to capture data returned from an FTP command.
+     */
+    void UUFTPCD_URC();
+
+    /** Helper function to get an FTP command as a text string, useful
+     * for debug purposes.
+     *
+     * @param ftpCmdCode  the FTP command.
+     * @return            FTP command in string format.
+     */
+    const char * getFtpCmd(FtpCmd ftpCmd);
+
+    /**********************************************************************
+     * PROTECTED: Cell Locate
+     **********************************************************************/
+
+    /**  The maximum number of hypotheses
+     */
+    #define CELL_MAX_HYP    (16 + 1)
+
+    /** Received positions.
+     */
+    volatile int _locRcvPos;
+
+    /** Expected positions.
+     */
+    volatile int _locExpPos;
+
+    /**  The Cell Locate data.
+     */
+    CellLocData _loc[CELL_MAX_HYP];
+
+    /** Buffer for the URC to work with
+     */
+    char urcBuf[128];
+
+    /** Callback to capture +UULOCIND.
+     */
+    void UULOCIND_URC();
+
+    /** Callback to capture +UULOC.
+     */
+    void UULOC_URC();
+};
+
+#endif // _UBLOX_AT_CELLULAR_INTERFACE_EXT_
+