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

UbloxATCellularInterfaceExt.cpp

Committer:
fahim.alavi@u-blox.com
Date:
2019-01-28
Revision:
17:ac64a6b90925
Parent:
15:6f0a1ecc8cec
Child:
28:4427f2e6bbab

File content as of revision 17:ac64a6b90925:

/* 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"
#ifdef FEATURE_COMMON_PAL
#include "mbed_trace.h"
#define TRACE_GROUP "UCAD"
#else
#define tr_debug(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__)
#define tr_info(format, ...)  debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__)
#define tr_warn(format, ...)  debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__)
#define tr_error(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__)
#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) {
        // Response type 1
        // +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_year -= 1900;
            _loc[0].time.tm_mon -= 1;
            _loc[0].time.tm_wday = 0;
            _loc[0].time.tm_yday = 0;
            _loc[0].validData = true;
            // Uncertainty can appear as 4294967, which is
            // (2^32 - 1) / 1000, or -1.  Since it is confusing
            // for the user to get a large positive number instead
            // of 0 -1, set it to -1 in th1s case.
            if (_loc[0].uncertainty == 4294967) {
                _loc[0].uncertainty = -1;
            }
            _locExpPos = 1;
            _locRcvPos++;
        // Response type 2, sensor used 1
        // +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_year -= 1900;
                _loc[a].time.tm_mon -= 1;
                _loc[a].time.tm_wday = 0;
                _loc[a].time.tm_yday = 0;
                // Uncertainty can appear as 4294967, which is
                // (2^32 - 1) / 1000, or -1.  Since it is confusing
                // for the user to get a large positive number instead
                // of 0 -1, set it to -1 in th1s case.
                if (_loc[a].uncertainty == 4294967) {
                    _loc[a].uncertainty = -1;
                }
                _loc[a].validData = true;
                _locRcvPos++;
            }
        // Response type 2, sensor used 2
        //+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_year -= 1900;
                _loc[a].time.tm_mon -= 1;
                _loc[a].time.tm_wday = 0;
                _loc[a].time.tm_yday = 0;
                _loc[a].speed = 0;
                _loc[a].direction = 0;
                _loc[a].verticalAcc = 0;
                _loc[a].svUsed = 0;
                // Uncertainty can appear as 4294967, which is
                // (2^32 - 1) / 1000, or -1.  Since it is confusing
                // for the user to get a large positive number instead
                // of 0 -1, set it to -1 in th1s case.
                if (_loc[a].uncertainty == 4294967) {
                    _loc[a].uncertainty = -1;
                }
                _loc[a].validData = true;
                _locRcvPos++;
            }
        // Response type 2, sensor used 0
        //+UULOC: <sol>,<num>,<sensor_used>,<date>,<time>,<lat>,<long>,<alt>,<uncertainty>
        } else if (sscanf(urcBuf, " %d,%d,%d,%d/%d/%d,%d:%d:%d.%*d,%f,%f,%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_year -= 1900;
                _loc[a].time.tm_mon -= 1;
                _loc[a].time.tm_wday = 0;
                _loc[a].time.tm_yday = 0;
                _loc[a].speed = 0;
                _loc[a].direction = 0;
                _loc[a].verticalAcc = 0;
                _loc[a].svUsed = 0;
                // Uncertainty can appear as 4294967, which is
                // (2^32 - 1) / 1000, or -1.  Since it is confusing
                // for the user to get a large positive number instead
                // of 0 -1, set it to -1 in th1s case.
                if (_loc[a].uncertainty == 4294967) {
                    _loc[a].uncertainty = -1;
                }
                _loc[a].validData = true;
                _locRcvPos++;
            }
        }
    }
}

/**********************************************************************
 * PUBLIC METHODS: GENERAL
 **********************************************************************/

// Constructor.
UbloxATCellularInterfaceExt::UbloxATCellularInterfaceExt(PinName tx,
                                                         PinName rx,
                                                         int baud,
                                                         bool debugOn,
                                                         osPriority priority):
                             UbloxATCellularInterface(tx, rx, baud, debugOn, priority)
{
    // 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, int *read_size)
{
    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;
            int read_length = 0;

            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) {
                        // Leave a short delay to make sure the file has been written
                        wait_ms(100);
                        // HTTP command successfully executed
                        read_length = readFile(rspFile, buf, len);

                        if (read_length >= 0) {
                            success = true;

                            if(read_size != NULL) {
                                *read_size = *read_size + read_length;
                            }
                        }
                    } 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.
// Note: not all modrmd support all parameters,
// hence the void return code.
void UbloxATCellularInterfaceExt::ftpResetPar()
{
    LOCK();
    debug_if(_debug_trace_on, "ftpResetPar()\n");
    for (int x = 0; x < NUM_FTP_OP_CODES; x++) {
        if (_at->send("AT+UFTP=%d", x)) {
            _at->recv("OK");
        }
    }
    UNLOCK();
}

// 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,
                                                                             char* buf, int len,
                                                                             int offset)
{
    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");
            
            // Not all modules support continuing a GET/PUT (e.g.
            // Sara-G350 00S-00 doesn't), so if we receive an error,
            // try again without it
            if (!atSuccess) {
                atSuccess = _at->send("AT+UFTPC=%d,\"%s\",\"%s\"",
                                      ftpCmd, file1, file2) &&
                            _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 but don't error on it as some
        // modules (e.g. Sara-G350) don't support it
        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 reacting to +ULOC

        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