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

Revision:
0:0b75e22c9231
Child:
1:26a67ab07275
--- /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
+