Implementation of the CellularInterface for u-blox C027 and C030 (non-N2xx flavour) modems that uses the IP stack on-board the cellular modem, hence not requiring LWIP (and so less RAM) and allowing any AT command exchanges to be carried out at the same time as data transfers (since the modem remains in AT mode all the time). This library may be used from mbed 5.5 onwards. If you need to use SMS, USSD or access the modem file system at the same time as using the CellularInterface then use ublox-at-cellular-interface-ext instead.

Dependents:   example-ublox-cellular-interface example-ublox-cellular-interface_r410M example-ublox-mbed-client example-ublox-cellular-interface ... more

UbloxATCellularInterface.cpp

Committer:
mudassar0121
Date:
10 months ago
Revision:
42:98808477a691
Parent:
41:69998003d95a

File content as of revision 42:98808477a691:

/* 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 "UbloxATCellularInterface.h"
#include "mbed_poll.h"
#include "nsapi.h"
#include "APN_db.h"
#ifdef FEATURE_COMMON_PAL
#include "mbed_trace.h"
#define TRACE_GROUP "UACI"
#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

/**********************************************************************
 * PRIVATE METHODS
 **********************************************************************/

// Event thread for asynchronous received data handling.
void UbloxATCellularInterface::handle_event(){
    pollfh fhs;
    int at_timeout;
    fhs.fh = _fh;
    fhs.events = POLLIN;

    while (_run_event_thread) {
    	int count;
        count = poll(&fhs, 1, 1000);
        if (count > 0 && (fhs.revents & POLLIN)) {
            LOCK();
            at_timeout = _at_timeout;
            at_set_timeout(10); // Avoid blocking but also make sure we don't
                                // time out if we get ahead of the serial port
            _at->debug_on(false); // Debug here screws with the test output
            // Let the URCs run
            _at->recv(UNNATURAL_STRING);
            _at->debug_on(_debug_trace_on);
            at_set_timeout(at_timeout);
            UNLOCK();
        }
    }
}

// Find or create a socket from the list.
UbloxATCellularInterface::SockCtrl * UbloxATCellularInterface::find_socket(int modem_handle)
{
    UbloxATCellularInterface::SockCtrl *socket = NULL;

    for (unsigned int x = 0; (socket == NULL) && (x < sizeof(_sockets) / sizeof(_sockets[0])); x++) {
        if (_sockets[x].modem_handle == modem_handle) {
            socket = &(_sockets[x]);
        }
    }

    return socket;
}

// Clear out the storage for a socket
void UbloxATCellularInterface::clear_socket(UbloxATCellularInterface::SockCtrl * socket)
{
    if (socket != NULL) {
        socket->modem_handle = SOCKET_UNUSED;
        socket->pending     = 0;
        socket->callback    = NULL;
        socket->data        = NULL;
    }
}

// Check that a socket pointer is valid
bool UbloxATCellularInterface::check_socket(SockCtrl * socket)
{
    bool success = false;

    if (socket != NULL) {
        for (unsigned int x = 0; !success && (x < sizeof(_sockets) / sizeof(_sockets[0])); x++) {
            if (socket == &(_sockets[x])) {
                success = true;
            }
        }
    }

    return success;
}

// Convert nsapi_security_t to the modem security numbers
int UbloxATCellularInterface::nsapi_security_to_modem_security(nsapi_security_t nsapi_security)
{
    int modem_security = 3;

    switch (nsapi_security)
    {
        case NSAPI_SECURITY_NONE:
            modem_security = 0;
            break;
        case NSAPI_SECURITY_PAP:
            modem_security = 1;
            break;
        case NSAPI_SECURITY_CHAP:
            modem_security = 2;
            break;
#ifndef TARGET_UBLOX_C030_R41XM
        case NSAPI_SECURITY_UNKNOWN:
            modem_security = 3;
            break;
        default:
            modem_security = 3;
            break;
#else
        default:
            modem_security = 0;
            break;
#endif
    }

    return modem_security;
}

// Callback for Socket Read URC.
void UbloxATCellularInterface::UUSORD_URC()
{
    int a;
    int b;
    char buf[32];
    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +UUSORD: <socket>,<length>
    if (read_at_to_char(buf, sizeof (buf), '\n') > 0) {

        if (sscanf(buf, ": %d,%d", &a, &b) == 2) {
        	SockCtrl *socket;
            socket = find_socket(a);
            if (socket != NULL) {
                socket->pending = b;
                // No debug prints here as they can affect timing
                // and cause data loss in UARTSerial
                if (socket->callback != NULL) {
                    socket->callback(socket->data);
                }
            }
        }
    }
}

// Callback for Socket Read From URC.
void UbloxATCellularInterface::UUSORF_URC()
{
    int a;
    int b;
    char buf[32];
    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +UUSORF: <socket>,<length>
    if (read_at_to_char(buf, sizeof (buf), '\n') > 0) {
        if (sscanf(buf, ": %d,%d", &a, &b) == 2) {
        	SockCtrl *socket;
            socket = find_socket(a);
            if (socket != NULL) {
                socket->pending = b;
                // No debug prints here as they can affect timing
                // and cause data loss in UARTSerial
                if (socket->callback != NULL) {
                    socket->callback(socket->data);
                }
            }
        }
    }
}

// Callback for Socket Close URC.
void UbloxATCellularInterface::UUSOCL_URC()
{
    int a;
    char buf[32];
    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +UUSOCL: <socket>
    if (read_at_to_char(buf, sizeof (buf), '\n') > 0) {
        if (sscanf(buf, ": %d", &a) == 1) {
        	SockCtrl *socket;
            socket = find_socket(a);
            tr_debug("Socket 0x%08x: handle %d closed by remote host",
                     (unsigned int) socket, a);
            clear_socket(socket);
        }
    }
}

// Callback for UUPSDD.
void UbloxATCellularInterface::UUPSDD_URC()
{
    int a;
    char buf[32];
    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +UUPSDD: <socket>
    if (read_at_to_char(buf, sizeof (buf), '\n') > 0) {
        if (sscanf(buf, ": %d", &a) == 1) {
        	SockCtrl *socket;
            socket = find_socket(a);
            tr_debug("Socket 0x%08x: handle %d connection lost",
                     (unsigned int) socket, a);
            clear_socket(socket);
            if (_connection_status_cb) {
                _connection_status_cb(NSAPI_ERROR_CONNECTION_LOST);
            }
        }
    }
}

/**********************************************************************
 * PROTECTED METHODS: GENERAL
 **********************************************************************/

// Get the next set of credentials, based on IMSI.
void UbloxATCellularInterface::get_next_credentials(const char ** config)
{
    if (*config) {
        _apn    = _APN_GET(*config);
        _uname  = _APN_GET(*config);
        _pwd    = _APN_GET(*config);
    }

    _apn    = _apn     ?  _apn    : "";
    _uname  = _uname   ?  _uname  : "";
    _pwd    = _pwd     ?  _pwd    : "";
}

// Active a connection profile on board the modem.
// Note: the AT interface should be locked before this is called.
bool UbloxATCellularInterface::activate_profile(const char* apn,
                                                const char* username,
                                                const char* password,
                                                nsapi_security_t auth)
{
    bool activated = false;
    bool success = false;
    int active = 0;
    int at_timeout = _at_timeout;
    SocketAddress address;

    // Set up the APN
    if (*apn) {
        success = _at->send("AT+UPSD=" PROFILE ",1,\"%s\"", apn) && _at->recv("OK");
    }
    if (success && *username) {
        success = _at->send("AT+UPSD=" PROFILE ",2,\"%s\"", username) && _at->recv("OK");
    }
    if (success && *password) {
        success = _at->send("AT+UPSD=" PROFILE ",3,\"%s\"", password) && _at->recv("OK");
    }

    if (success) {
        // Set up dynamic IP address assignment.
        success = _at->send("AT+UPSD=" PROFILE ",7,\"0.0.0.0\"") && _at->recv("OK");
        // Set up the authentication protocol
        // 0 = none
        // 1 = PAP (Password Authentication Protocol)
        // 2 = CHAP (Challenge Handshake Authentication Protocol)
        for (int protocol = nsapi_security_to_modem_security(NSAPI_SECURITY_NONE);
             success && (protocol <= nsapi_security_to_modem_security(NSAPI_SECURITY_CHAP)); protocol++) {
            if ((_auth == NSAPI_SECURITY_UNKNOWN) || (nsapi_security_to_modem_security(_auth) == protocol)) {
                if (_at->send("AT+UPSD=" PROFILE ",6,%d", protocol) && _at->recv("OK")) {
                    at_set_timeout(3*60*1000);
                    _at->send("AT+UPSDA=" PROFILE ",3") && _at->recv("OK");
                    at_set_timeout(at_timeout);
                }
            }
        }

        Timer timer;
        timer.start();
        while (timer.read() < 180) {
            if (_at->send("AT+UPSND=" PROFILE ",8") && _at->recv("+UPSND: %*d,%*d,%d\n", &active) &&
                _at->recv("OK")) {
                if (active == 1) {
                    tr_debug("Profile activated successfully");
                    activated = true;
                    break;
                } else {
                    tr_error("Profile still inactive");
                    rtos::ThisThread::sleep_for(5000); //Wait for 5 seconds and then try again
                }
            }
        }
        timer.stop();
    }

    return activated;
}

// Activate a profile by reusing an external PDP context.
// Note: the AT interface should be locked before this is called.
bool UbloxATCellularInterface::activate_profile_reuse_external(void)
{
    bool success = false;
    int cid = -1;
    SocketAddress address;
    int t;
    int at_timeout = _at_timeout;

    //+CGDCONT: <cid>,"IP","<apn name>","<ip adr>",0,0,0,0,0,0
    if (_at->send("AT+CGDCONT?")) {
    	char ip[NSAPI_IP_SIZE];
        if (_at->recv("+CGDCONT: %d,\"IP\",\"%*[^\"]\",\"%" u_stringify(NSAPI_IP_SIZE) "[^\"]\",%*d,%*d,%*d,%*d,%*d,%*d",
                      &t, ip) &&
            _at->recv("OK")) {
            // Check if the IP address is valid
            if (address.set_ip_address(ip)) {
                cid = t;
            }
        }
    }

    // If a context has been found, use it
    if ((cid != -1) && (_at->send("AT+UPSD=" PROFILE ",100,%d", cid) && _at->recv("OK"))) {
        // Activate, waiting 30 seconds for the connection to be made
        at_set_timeout(30000);
        success = _at->send("AT+UPSDA=" PROFILE ",3") && _at->recv("OK");
        at_set_timeout(at_timeout);
    }

    return success;
}

// Activate a profile by context ID.
// Note: the AT interface should be locked before this is called.
bool UbloxATCellularInterface::activate_profile_by_cid(int cid,
                                                       const char* apn,
                                                       const char* username,
                                                       const char* password,
                                                       nsapi_security_t auth)
{
    bool success = false;

#ifndef TARGET_UBLOX_C030_R41XM
    int at_timeout = _at_timeout;

    if (_at->send("AT+CGDCONT=%d,\"IP\",\"%s\"", cid, apn) && _at->recv("OK") &&
        _at->send("AT+UAUTHREQ=%d,%d,\"%s\",\"%s\"", cid, nsapi_security_to_modem_security(auth),
                  username, password) && _at->recv("OK") &&
        _at->send("AT+UPSD=" PROFILE ",100,%d", cid) && _at->recv("OK")) {

        // Wait 30 seconds for the connection to be made
        at_set_timeout(30000);
        // Activate the protocol
        success = _at->send("AT+UPSDA=" PROFILE ",3") && _at->recv("OK");
        at_set_timeout(at_timeout);
    }
#else
    int modem_security = nsapi_security_to_modem_security(auth);
    if (_at->send("AT+CGDCONT=%d,\"IP\",\"%s\"", cid, apn) && _at->recv("OK")) {
        if ( modem_security == NSAPI_SECURITY_CHAP) {
            if (_at->send("AT+UAUTHREQ=%d,%d,\"%s\",\"%s\"", cid, modem_security,
                        password, username) && _at->recv("OK")) {
                success = true;
            }
        } else if (modem_security == NSAPI_SECURITY_NONE) {
            if (_at->send("AT+UAUTHREQ=%d,%d", cid, modem_security) && _at->recv("OK")) {
                success = true;
            }
        } else {
            if (_at->send("AT+UAUTHREQ=%d,%d,\"%s\",\"%s\"", cid, modem_security,
                          username, password) && _at->recv("OK")) {
                success = true;
            }
        }
    }
#endif

    return success;
}

#ifdef TARGET_UBLOX_C030_R41XM
//define PDP context for R412M
bool UbloxATCellularInterface::define_context()
{
    bool success = false;
    const char * config = NULL;
    LOCK();

    // If the caller hasn't entered an APN, try to find it
    if (_apn == NULL) {
        config = apnconfig(_dev_info.imsi);
    }

    // Attempt to connect
    do {
        // Set up APN and IP protocol for PDP context
        get_next_credentials(&config);
        _auth = (*_uname && *_pwd) ? _auth : NSAPI_SECURITY_NONE;
        if (activate_profile_by_cid(1, _apn, _uname, _pwd, _auth)) {
            success = true;
        }
    } while (!success && config && *config);

    if (!success) {
        tr_error("Failed to connect, check your APN/username/password");
    }

    UNLOCK();
    return success;
}
bool UbloxATCellularInterface::activate_context()
{
    bool status = false;
    int at_timeout;
    LOCK();

    at_timeout = _at_timeout; // Has to be inside LOCK()s
    at_set_timeout(150000);
    if (_at->send("AT+CGACT=1,1") && _at->recv("OK")) {
        status = true;
    }

    at_set_timeout(at_timeout);

    UNLOCK();
    return status;
}

bool UbloxATCellularInterface::is_context_active()
{
    bool status = false;
    int at_timeout;
    int active = 0;
    LOCK();

    at_timeout = _at_timeout; // Has to be inside LOCK()s
    at_set_timeout(150000);
    if (_at->send("AT+CGACT?") && _at->recv("+CGACT: %*d,%d\n", &active) &&
            _at->recv("OK")) {
        if (active == 1) {
        	status = true;
        }
    }
    at_set_timeout(at_timeout);

    UNLOCK();
    return status;
}
#endif

// Connect the on board IP stack of the modem.
bool UbloxATCellularInterface::connect_modem_stack()
{
    bool success = false;
    int active = 0;
    const char * config = NULL;
    LOCK();

    // Check the profile
    if (_at->send("AT+UPSND=" PROFILE ",8") && _at->recv("+UPSND: %*d,%*d,%d\n", &active) &&
        _at->recv("OK")) {
        if (active == 0) {
            // If the caller hasn't entered an APN, try to find it
            if (_apn == NULL) {
                config = apnconfig(_dev_info.imsi);
            }

            // Attempt to connect
            do {
                // Set up APN and IP protocol for PDP context
                get_next_credentials(&config);
                _auth = (*_uname && *_pwd) ? _auth : NSAPI_SECURITY_NONE;
                if ((_dev_info.dev != DEV_TOBY_L2) && (_dev_info.dev != DEV_MPCI_L2)) {
                    success = activate_profile(_apn, _uname, _pwd, _auth);
                } else {
                    success = activate_profile_reuse_external();
                    if (success) {
                        tr_debug("Reusing external context");
                    } else {
                        success = activate_profile_by_cid(1, _apn, _uname, _pwd, _auth);
                    }
                }
            } while (!success && config && *config);
        } else {
            // If the profile is already active, we're good
            success = true;
        }
    }

    if (!success) {
        tr_error("Failed to connect, check your APN/username/password");
    }

    UNLOCK();
    return success;
}

// Disconnect the on board IP stack of the modem.
bool UbloxATCellularInterface::disconnect_modem_stack()
{
    bool success = false;
    int active;
    Timer t1;
    LOCK();

    if (get_ip_address() != NULL) {
        if (_at->send("AT+UPSDA=" PROFILE ",4") && _at->recv("OK")) {
            success = true;
            if (_connection_status_cb) {
                _connection_status_cb(NSAPI_ERROR_CONNECTION_LOST);
            }
        }
    }
    t1.start();
    while (!(t1.read() >= 180)) {
        if (_at->send("AT+UPSND=" PROFILE ",8") && _at->recv("+UPSND: %*d,%*d,%d\n", &active) && _at->recv("OK")) {

            if (active == 0) {  //If context is deactivated, exit while loop and return status
                tr_debug("Profile deactivated successfully");
                break;
            }
            else {
                tr_error("Profile still active");
                rtos::ThisThread::sleep_for(5000); //Wait for 5 seconds and then try again
            }
        }
    }
    t1.stop();

    UNLOCK();
    return success;
}

/**********************************************************************
 * PROTECTED METHODS: NETWORK INTERFACE and SOCKETS
 **********************************************************************/

// Gain access to us.
NetworkStack *UbloxATCellularInterface::get_stack()
{
    return this;
}

// Create a socket.
nsapi_error_t UbloxATCellularInterface::socket_open(nsapi_socket_t *handle,
                                                    nsapi_protocol_t proto)
{
    nsapi_error_t nsapi_error = NSAPI_ERROR_DEVICE_ERROR;
    int modem_handle;
    SockCtrl *socket;
    LOCK();

    // Find a free socket
    socket = find_socket();
    tr_debug("socket_open(%d)", proto);

    if (socket != NULL) {
    	bool success = false;
        if (proto == NSAPI_UDP) {
            success = _at->send("AT+USOCR=17");
        } else if (proto == NSAPI_TCP) {
            success = _at->send("AT+USOCR=6");
        } else  {
            nsapi_error = NSAPI_ERROR_UNSUPPORTED;
        }

        if (success) {
            nsapi_error = NSAPI_ERROR_NO_SOCKET;
            if (_at->recv("+USOCR: %d\n", &modem_handle) && (modem_handle != SOCKET_UNUSED) &&
                _at->recv("OK")) {
                tr_debug("Socket 0x%8x: handle %d was created", (unsigned int) socket, modem_handle);
                clear_socket(socket);
                socket->modem_handle         = modem_handle;
                *handle = (nsapi_socket_t) socket;
                nsapi_error = NSAPI_ERROR_OK;
            }
        }
    } else {
        nsapi_error = NSAPI_ERROR_NO_MEMORY;
    }

    UNLOCK();
    return nsapi_error;
}

// Close a socket.
nsapi_error_t UbloxATCellularInterface::socket_close(nsapi_socket_t handle)
{
    nsapi_error_t nsapi_error = NSAPI_ERROR_DEVICE_ERROR;
    SockCtrl *socket = (SockCtrl *) handle;
    LOCK();

    tr_debug("socket_close(0x%08x)", (unsigned int) handle);

    MBED_ASSERT (check_socket(socket));

    if (_at->send("AT+USOCL=%d", socket->modem_handle) &&
        _at->recv("OK")) {
        clear_socket(socket);
        nsapi_error = NSAPI_ERROR_OK;
    }

    UNLOCK();
    return nsapi_error;
}

// Bind a local port to a socket.
nsapi_error_t UbloxATCellularInterface::socket_bind(nsapi_socket_t handle,
                                                    const SocketAddress &address)
{
    nsapi_error_t nsapi_error = NSAPI_ERROR_NO_SOCKET;
    int proto;
    int modem_handle;
    SockCtrl savedSocket;
    SockCtrl *socket = (SockCtrl *) handle;
    LOCK();

    tr_debug("socket_bind(0x%08x, :%d)", (unsigned int) handle, address.get_port());

    MBED_ASSERT (check_socket(socket));

    // Query the socket type
    if (_at->send("AT+USOCTL=%d,0", socket->modem_handle) &&
        _at->recv("+USOCTL: %*d,0,%d\n", &proto) &&
        _at->recv("OK")) {
        savedSocket = *socket;
        nsapi_error = NSAPI_ERROR_DEVICE_ERROR;
        // Now close the socket and re-open it with the binding given
        if (_at->send("AT+USOCL=%d", socket->modem_handle) &&
            _at->recv("OK")) {
            clear_socket(socket);
            nsapi_error = NSAPI_ERROR_CONNECTION_LOST;
            if (_at->send("AT+USOCR=%d,%d", proto, address.get_port()) &&
                _at->recv("+USOCR: %d\n", &modem_handle) && (modem_handle != SOCKET_UNUSED) &&
                _at->recv("OK")) {
                *socket = savedSocket;
                nsapi_error = NSAPI_ERROR_OK;
            }
        }
    }

    UNLOCK();
    return nsapi_error;
}

// Connect to a socket
nsapi_error_t UbloxATCellularInterface::socket_connect(nsapi_socket_t handle,
                                                       const SocketAddress &address)
{
    nsapi_error_t nsapi_error = NSAPI_ERROR_DEVICE_ERROR;
    SockCtrl *socket = (SockCtrl *) handle;
    LOCK();

    tr_debug("socket_connect(0x%08x, %s(:%d))", (unsigned int) handle,
             address.get_ip_address(), address.get_port());

    MBED_ASSERT (check_socket(socket));

    if (_at->send("AT+USOCO=%d,\"%s\",%d", socket->modem_handle,
                  address.get_ip_address(), address.get_port()) &&
        _at->recv("OK")) {
        nsapi_error = NSAPI_ERROR_OK;
    }

    UNLOCK();
    return nsapi_error;
}

// Send to a socket.
nsapi_size_or_error_t UbloxATCellularInterface::socket_send(nsapi_socket_t handle,
                                                            const void *data,
                                                            nsapi_size_t size)
{
    nsapi_size_or_error_t nsapi_error_size = NSAPI_ERROR_DEVICE_ERROR;
    bool success = true;
    const char *buf = (const char *) data;
    nsapi_size_t blk = MAX_WRITE_SIZE;
    nsapi_size_t count = size;
    SockCtrl *socket = (SockCtrl *) handle;

    tr_debug("socket_send(0x%08x, 0x%08x, %d)", (unsigned int) handle, (unsigned int) data, size);

    MBED_ASSERT (check_socket(socket));

    if (socket->modem_handle == SOCKET_UNUSED) {
        tr_debug("socket_send: socket closed");
        return NSAPI_ERROR_NO_SOCKET;
    }

    while ((count > 0) && success) {
        if (count < blk) {
            blk = count;
        }
        LOCK();

        if (_at->send("AT+USOWR=%d,%d", socket->modem_handle, blk) && _at->recv("@")) {
            ThisThread::sleep_for(50);
            if ((_at->write(buf, blk) < (int) blk) ||
                 !_at->recv("OK")) {
                success = false;
            }
        } else {
            success = false;
        }

        UNLOCK();
        buf += blk;
        count -= blk;
    }

    if (success) {
        nsapi_error_size = size - count;
        if (_debug_trace_on) {
            tr_debug("socket_send: %d \"%*.*s\"", size, size, size, (char *) data);
        }
    }

    return nsapi_error_size;
}

// Send to an IP address.
nsapi_size_or_error_t UbloxATCellularInterface::socket_sendto(nsapi_socket_t handle,
                                                              const SocketAddress &address,
                                                              const void *data,
                                                              nsapi_size_t size)
{
    nsapi_size_or_error_t nsapi_error_size = NSAPI_ERROR_DEVICE_ERROR;
    bool success = true;
    const char *buf = (const char *) data;
    nsapi_size_t blk = MAX_WRITE_SIZE;
    nsapi_size_t count = size;
    SockCtrl *socket = (SockCtrl *) handle;

    tr_debug("socket_sendto(0x%8x, %s(:%d), 0x%08x, %d)", (unsigned int) handle,
             address.get_ip_address(), address.get_port(), (unsigned int) data, size);

    MBED_ASSERT (check_socket(socket));

    if (size > MAX_WRITE_SIZE) {
        tr_warn("WARNING: packet length %d is too big for one UDP packet (max %d), will be fragmented.", size, MAX_WRITE_SIZE);
    }

    while ((count > 0) && success) {
        if (count < blk) {
            blk = count;
        }
        LOCK();

        if (_at->send("AT+USOST=%d,\"%s\",%d,%d", socket->modem_handle,
                      address.get_ip_address(), address.get_port(), blk) &&
            _at->recv("@")) {
            ThisThread::sleep_for(50);
            if ((_at->write(buf, blk) >= (int) blk) &&
                 _at->recv("OK")) {
            } else {
                success = false;
            }
        } else {
            success = false;
        }

        UNLOCK();
        buf += blk;
        count -= blk;
    }

    if (success) {
        nsapi_error_size = size - count;
        if (_debug_trace_on) {
            tr_debug("socket_sendto: %d \"%*.*s\"", size, size, size, (char *) data);
        }
    }

    return nsapi_error_size;
}

// Receive from a socket, TCP style.
nsapi_size_or_error_t UbloxATCellularInterface::socket_recv(nsapi_socket_t handle,
                                                            void *data,
                                                            nsapi_size_t size)
{
    nsapi_size_or_error_t nsapi_error_size = NSAPI_ERROR_DEVICE_ERROR;
    bool success = true;
    char *buf = (char *) data;
    nsapi_size_t read_blk;
    nsapi_size_t count = 0;
    unsigned int usord_sz;
    int read_sz;
    Timer timer;
    SockCtrl *socket = (SockCtrl *) handle;


    tr_debug("socket_recv(0x%08x, 0x%08x, %d)",
             (unsigned int) handle, (unsigned int) data, size);

    MBED_ASSERT (check_socket(socket));

    if (socket->modem_handle == SOCKET_UNUSED) {
        tr_debug("socket_recv: socket closed");
        return NSAPI_ERROR_NO_SOCKET;
    }

    timer.start();

    while (success && (size > 0)) {
    	int at_timeout;
        LOCK();
        at_timeout = _at_timeout;
        at_set_timeout(1000);

        read_blk = MAX_READ_SIZE;
        if (read_blk > size) {
            read_blk = size;
        }
        if (socket->pending > 0) {
            tr_debug("Socket 0x%08x: modem handle %d has %d byte(s) pending",
                     (unsigned int) socket, socket->modem_handle, socket->pending);
            _at->debug_on(false); // ABSOLUTELY no time for debug here if you want to
                                  // be able to read packets of any size without
                                  // losing characters in UARTSerial
            if (_at->send("AT+USORD=%d,%d", socket->modem_handle, read_blk) &&
                _at->recv("+USORD: %*d,%d,\"", &usord_sz)) {
                // Must use what +USORD returns here as it may be less or more than we asked for
                if (usord_sz > socket->pending) {
                    socket->pending = 0;
                } else {
                    socket->pending -= usord_sz; 
                }
                // Note: insert no debug between _at->recv() and _at->read(), no time...
                if (usord_sz > size) {
                    usord_sz = size;
                }
                read_sz = _at->read(buf, usord_sz);
                if (read_sz > 0) {
                    tr_debug("...read %d byte(s) from modem handle %d...", read_sz,
                             socket->modem_handle);
                    if (_debug_trace_on) {
                        tr_debug("Read returned %d,  |%*.*s|", read_sz, read_sz, read_sz, buf);
                    }
                    count += read_sz;
                    buf += read_sz;
                    size -= read_sz;
                } else {
                    // read() should not fail
                    success = false;
                }
                tr_debug("Socket 0x%08x: modem handle %d now has only %d byte(s) pending",
                         (unsigned int) socket, socket->modem_handle, socket->pending);
                // Wait for the "OK" before continuing
                _at->recv("OK");
            } else {
                // Should never fail to do _at->send()/_at->recv()
                success = false;
            }
            _at->debug_on(_debug_trace_on);
        } else if (timer.read_ms() < SOCKET_TIMEOUT) {
            // Wait for URCs
            _at->recv(UNNATURAL_STRING);
        } else {
            if (count == 0) {
                // Timeout with nothing received
                nsapi_error_size = NSAPI_ERROR_WOULD_BLOCK;
                success = false;
            }
            size = 0; // This simply to cause an exit
        }

        at_set_timeout(at_timeout);
        UNLOCK();
    }
    timer.stop();

    if (success) {
        nsapi_error_size = count;
    }

    if (_debug_trace_on) {
        tr_debug("socket_recv: %d \"%*.*s\"", count, count, count, buf - count);
    } else {
        tr_debug("socket_recv: received %d byte(s)", count);
    }

    return nsapi_error_size;
}

// Receive a packet over a UDP socket.
nsapi_size_or_error_t UbloxATCellularInterface::socket_recvfrom(nsapi_socket_t handle,
                                                                SocketAddress *address,
                                                                void *data,
                                                                nsapi_size_t size)
{
    nsapi_size_or_error_t nsapi_error_size = NSAPI_ERROR_DEVICE_ERROR;
    bool success = true;
    char *buf = (char *) data;
    nsapi_size_t read_blk;
    nsapi_size_t count = 0;
    char ipAddress[NSAPI_IP_SIZE];
    int port;
    unsigned int usorf_sz;
    int read_sz;
    Timer timer;
    SockCtrl *socket = (SockCtrl *) handle;

    tr_debug("socket_recvfrom(0x%08x, 0x%08x, %d)",
             (unsigned int) handle, (unsigned int) data, size);

    MBED_ASSERT (check_socket(socket));

    timer.start();

    while (success && (size > 0)) {
    	int at_timeout;
        LOCK();
        at_timeout = _at_timeout;
        at_set_timeout(1000);

        read_blk = MAX_READ_SIZE;
        if (read_blk > size) {
            read_blk = size;
        }
        if (socket->pending > 0) {
            tr_debug("Socket 0x%08x: modem handle %d has %d byte(s) pending",
                     (unsigned int) socket, socket->modem_handle, socket->pending);
            memset (ipAddress, 0, sizeof (ipAddress)); // Ensure terminator

            // Note: the maximum length of UDP packet we can receive comes from
            // fitting all of the following into one buffer:
            //
            // +USORF: xx,"max.len.ip.address.ipv4.or.ipv6",yyyyy,wwww,"the_data"\r\n
            //
            // where xx is the handle, max.len.ip.address.ipv4.or.ipv6 is NSAPI_IP_SIZE,
            // yyyyy is the port number (max 65536), wwww is the length of the data and
            // the_data is binary data. I make that 29 + 48 + len(the_data),
            // so the overhead is 77 bytes.

            _at->debug_on(false); // ABSOLUTELY no time for debug here if you want to
                                  // be able to read packets of any size without
                                  // losing characters in UARTSerial
            if (_at->send("AT+USORF=%d,%d", socket->modem_handle, read_blk) &&
                _at->recv("+USORF: %*d,\"%" u_stringify(NSAPI_IP_SIZE) "[^\"]\",%d,%d,\"",
                          ipAddress, &port, &usorf_sz)) {
                // Must use what +USORF returns here as it may be less or more than we asked for
                if (usorf_sz > socket->pending) {
                    socket->pending = 0;
                } else {
                    socket->pending -= usorf_sz; 
                }
                // Note: insert no debug between _at->recv() and _at->read(), no time...
                if (usorf_sz > size) {
                    usorf_sz = size;
                }
                read_sz = _at->read(buf, usorf_sz);
                if (read_sz > 0) {
                    address->set_ip_address(ipAddress);
                    address->set_port(port);
                    tr_debug("...read %d byte(s) from modem handle %d...", read_sz,
                             socket->modem_handle);
                    if (_debug_trace_on) {
                        tr_debug("Read returned %d,  |%*.*s|", read_sz, read_sz, read_sz, buf);
                    }
                    count += read_sz;
                    buf += read_sz;
                    size -= read_sz;
                    if ((usorf_sz < read_blk) || (usorf_sz == MAX_READ_SIZE)) {
                        size = 0; // If we've received less than we asked for, or
                                  // the max size, then a whole UDP packet has arrived and
                                  // this means DONE.
                    }
                } else {
                    // read() should not fail
                    success = false;
                }
                tr_debug("Socket 0x%08x: modem handle %d now has only %d byte(s) pending",
                         (unsigned int) socket, socket->modem_handle, socket->pending);
                // Wait for the "OK" before continuing
                _at->recv("OK");
            } else {
                // Should never fail to do _at->send()/_at->recv()
                success = false;
            }
            _at->debug_on(_debug_trace_on);
        } else if (timer.read_ms() < SOCKET_TIMEOUT) {
            // Wait for URCs
            _at->recv(UNNATURAL_STRING);
        } else {
            if (count == 0) {
                // Timeout with nothing received
                nsapi_error_size = NSAPI_ERROR_WOULD_BLOCK;
                success = false;
            }
            size = 0; // This simply to cause an exit
        }

        at_set_timeout(at_timeout);
        UNLOCK();
    }
    timer.stop();

    if (success) {
        nsapi_error_size = count;
    }

    if (_debug_trace_on) {
        tr_debug("socket_recvfrom: %d \"%*.*s\"", count, count, count, buf - count);
    } else {
        tr_debug("socket_recvfrom: received %d byte(s)", count);
    }

    return nsapi_error_size;
}

// Attach an event callback to a socket, required for asynchronous
// data reception
void UbloxATCellularInterface::socket_attach(nsapi_socket_t handle,
                                             void (*callback)(void *),
                                             void *data)
{
    SockCtrl *socket = (SockCtrl *) handle;

    MBED_ASSERT (check_socket(socket));

    socket->callback = callback;
    socket->data = data;
}

// Unsupported TCP server functions.
nsapi_error_t UbloxATCellularInterface::socket_listen(nsapi_socket_t handle,
                                                      int backlog)
{
    return NSAPI_ERROR_UNSUPPORTED;
}
nsapi_error_t UbloxATCellularInterface::socket_accept(nsapi_socket_t server,
                                                      nsapi_socket_t *handle,
                                                      SocketAddress *address)
{
    return NSAPI_ERROR_UNSUPPORTED;
}

// Unsupported option functions.
nsapi_error_t UbloxATCellularInterface::setsockopt(nsapi_socket_t handle,
                                                   int level, int optname,
                                                   const void *optval,
                                                   unsigned optlen)
{
    return NSAPI_ERROR_UNSUPPORTED;
}
nsapi_error_t UbloxATCellularInterface::getsockopt(nsapi_socket_t handle,
                                                   int level, int optname,
                                                   void *optval,
                                                   unsigned *optlen)
{
    return NSAPI_ERROR_UNSUPPORTED;
}

/**********************************************************************
 * PUBLIC METHODS
 **********************************************************************/

// Constructor.
UbloxATCellularInterface::UbloxATCellularInterface(PinName tx,
                                                   PinName rx,
                                                   int baud,
                                                   bool debug_on,
                                                   osPriority priority)
{
    _sim_pin_check_change_pending = false;
    _sim_pin_check_change_pending_enabled_value = false;
    _sim_pin_change_pending = false;
    _sim_pin_change_pending_new_pin_value = NULL;
    _run_event_thread = true;
    _apn = NULL;
    _uname = NULL;
    _pwd = NULL;




    // Initialise sockets storage
    memset(_sockets, 0, sizeof(_sockets));
    for (unsigned int socket = 0; socket < sizeof(_sockets) / sizeof(_sockets[0]); socket++) {
        _sockets[socket].modem_handle = SOCKET_UNUSED;
        _sockets[socket].callback = NULL;
        _sockets[socket].data = NULL;
    }

    // The authentication to use
    _auth = NSAPI_SECURITY_UNKNOWN;

    // Nullify the temporary IP address storage
    _ip = NULL;
    _connection_status_cb = NULL;


    // Initialise the base class, which starts the AT parser
    baseClassInit(tx, rx, baud, debug_on);

    // Start the event handler thread for Rx data
    event_thread.start(callback(this, &UbloxATCellularInterface::handle_event));
    event_thread.set_priority(priority);

    // URC handlers for sockets
    _at->oob("+UUSORD", callback(this, &UbloxATCellularInterface::UUSORD_URC));
    _at->oob("+UUSORF", callback(this, &UbloxATCellularInterface::UUSORF_URC));
    _at->oob("+UUSOCL", callback(this, &UbloxATCellularInterface::UUSOCL_URC));
    _at->oob("+UUPSDD", callback(this, &UbloxATCellularInterface::UUPSDD_URC));
}

// Destructor.
UbloxATCellularInterface::~UbloxATCellularInterface()
{
    // Let the event thread shut down tidily
    _run_event_thread = false;
    event_thread.join();
    
    // Free _ip if it was ever allocated
    free(_ip);
}

// Set the authentication scheme.
void UbloxATCellularInterface::set_authentication(nsapi_security_t auth)
{
    _auth = auth;
}

// Set APN, user name and password.
void  UbloxATCellularInterface::set_credentials(const char *apn,
                                                const char *uname,
                                                const char *pwd)
{
    _apn = apn;
    _uname = uname;
    _pwd = pwd;
}

// Set PIN.
void UbloxATCellularInterface::set_sim_pin(const char *pin)
{
    set_pin(pin);
}

// Get the IP address of a host.
nsapi_error_t UbloxATCellularInterface::gethostbyname(const char *host,
                                                      SocketAddress *address,
                                                      nsapi_version_t version)
{
    nsapi_error_t nsapi_error = NSAPI_ERROR_DEVICE_ERROR;


    if (address->set_ip_address(host)) {
        nsapi_error = NSAPI_ERROR_OK;
    } else {
    	int at_timeout;
    	char ipAddress[NSAPI_IP_SIZE];
        LOCK();
        // This interrogation can sometimes take longer than the usual 8 seconds
        at_timeout = _at_timeout;
        at_set_timeout(60000);
        memset (ipAddress, 0, sizeof (ipAddress)); // Ensure terminator
        if (_at->send("AT+UDNSRN=0,\"%s\"", host) &&
            _at->recv("+UDNSRN: \"%" u_stringify(NSAPI_IP_SIZE) "[^\"]\"", ipAddress) &&
            _at->recv("OK")) {
            if (address->set_ip_address(ipAddress)) {
                nsapi_error = NSAPI_ERROR_OK;
            }
        }
        at_set_timeout(at_timeout);
        UNLOCK();
    }

    return nsapi_error;
}

// Make a cellular connection
nsapi_error_t UbloxATCellularInterface::connect(const char *sim_pin,
                                                const char *apn,
                                                const char *uname,
                                                const char *pwd)
{
    nsapi_error_t nsapi_error;

    if (sim_pin != NULL) {
        _pin = sim_pin;
    }

    if (apn != NULL) {
        _apn = apn;
    }

    if ((uname != NULL) && (pwd != NULL)) {
        _uname = uname;
        _pwd = pwd;
    } else {
        _uname = NULL;
        _pwd = NULL;
    }

    nsapi_error = connect();

    return nsapi_error;
}

// Make a cellular connection using the IP stack on board the cellular modem
nsapi_error_t UbloxATCellularInterface::connect()
{
    nsapi_error_t nsapi_error = NSAPI_ERROR_DEVICE_ERROR;
    bool registered = false;

    // Set up modem and then register with the network
    if (init()) {
        nsapi_error = NSAPI_ERROR_NO_CONNECTION;
        // Perform any pending SIM actions
        if (_sim_pin_check_change_pending) {
            if (!sim_pin_check_enable(_sim_pin_check_change_pending_enabled_value)) {
                nsapi_error = NSAPI_ERROR_AUTH_FAILURE;
            }
            _sim_pin_check_change_pending = false;
        }
        if (_sim_pin_change_pending) {
            if (!change_sim_pin(_sim_pin_change_pending_new_pin_value)) {
                nsapi_error = NSAPI_ERROR_AUTH_FAILURE;
            }
            _sim_pin_change_pending = false;
        }
#ifdef TARGET_UBLOX_C030_R41XM
        define_context();
#endif
        if (nsapi_error == NSAPI_ERROR_NO_CONNECTION) {
            for (int retries = 0; !registered && (retries < 3); retries++) {
                if (nwk_registration()) {
                    registered = true;
#ifdef TARGET_UBLOX_C030_R41XM
                    if (is_context_active()) {
                        nsapi_error = NSAPI_ERROR_OK;
                        tr_info("Context is already active\r\n");
                    } else {
                        tr_info("Context is not active, activating it.\r\n");
                        if (activate_context()) {
                            nsapi_error = NSAPI_ERROR_OK;
                        }
                    }
#endif
                }
            }
        }
    }
#ifndef TARGET_UBLOX_C030_R41XM
    // Attempt to establish a connection
    if (registered && connect_modem_stack()) {
        nsapi_error = NSAPI_ERROR_OK;
    }
#endif
    return nsapi_error;
}

// User initiated disconnect.
nsapi_error_t UbloxATCellularInterface::disconnect()
{
    nsapi_error_t nsapi_error = NSAPI_ERROR_DEVICE_ERROR;
#ifdef TARGET_UBLOX_C030_R41XM
    if (nwk_deregistration()) {
        if (_connection_status_cb) {
            _connection_status_cb(NSAPI_ERROR_CONNECTION_LOST);
        }
#else
    if (disconnect_modem_stack() && nwk_deregistration()) {
#endif
        nsapi_error = NSAPI_ERROR_OK;
    }

    return nsapi_error;
}

// Enable or disable SIM PIN check lock.
nsapi_error_t UbloxATCellularInterface::set_sim_pin_check(bool set,
                                                          bool immediate,
                                                          const char *sim_pin)
{
    nsapi_error_t nsapi_error = NSAPI_ERROR_AUTH_FAILURE;

    if (sim_pin != NULL) {
        _pin = sim_pin;
    }

    if (immediate) {
        if (init()) {
            if (sim_pin_check_enable(set)) {
                nsapi_error = NSAPI_ERROR_OK;
            }
        } else {
            nsapi_error = NSAPI_ERROR_DEVICE_ERROR;
        }
    } else {
        nsapi_error = NSAPI_ERROR_OK;
        _sim_pin_check_change_pending = true;
        _sim_pin_check_change_pending_enabled_value = set;
    }

    return nsapi_error;
}

// Change the PIN code for the SIM card.
nsapi_error_t UbloxATCellularInterface::set_new_sim_pin(const char *new_pin,
                                                        bool immediate,
                                                        const char *old_pin)
{
    nsapi_error_t nsapi_error = NSAPI_ERROR_AUTH_FAILURE;

    if (old_pin != NULL) {
        _pin = old_pin;
    }

    if (immediate) {
        if (init()) {
            if (change_sim_pin(new_pin)) {
                nsapi_error = NSAPI_ERROR_OK;
            }
        } else {
            nsapi_error = NSAPI_ERROR_DEVICE_ERROR;
        }
    } else {
        nsapi_error = NSAPI_ERROR_OK;
        _sim_pin_change_pending = true;
        _sim_pin_change_pending_new_pin_value = new_pin;
    }

    return nsapi_error;
}

// Determine if the connection is up.
bool UbloxATCellularInterface::is_connected()
{
    return get_ip_address() != NULL;
}

// Get the IP address of the on-board modem IP stack.
const char * UbloxATCellularInterface::get_ip_address()
{
    SocketAddress address;
    LOCK();

    if (_ip == NULL) {
        // Temporary storage for an IP address string with terminator
        _ip = (char *) malloc(NSAPI_IP_SIZE);
    }

    if (_ip != NULL) {
        memset(_ip, 0, NSAPI_IP_SIZE); // Ensure a terminator
        // +UPSND=<profile_id>,<param_tag>[,<dynamic_param_val>]
        // If we get back a quoted "w.x.y.z" then we have an IP address,
        // otherwise we don't.
#ifdef TARGET_UBLOX_C030_R41XM
        if (!_at->send("AT+CGPADDR=1") ||
            !_at->recv("+CGPADDR: 1,%" u_stringify(NSAPI_IP_SIZE) "[^\n]\nOK\n", _ip) ||
            !address.set_ip_address(_ip) || // Return NULL if the address is not a valid one
            !address) { // Return null if the address is zero
            free (_ip);
            _ip = NULL;
        }
#else
        if (!_at->send("AT+UPSND=" PROFILE ",0") ||
            !_at->recv("+UPSND: " PROFILE ",0,\"%" u_stringify(NSAPI_IP_SIZE) "[^\"]\"", _ip) ||
            !_at->recv("OK") ||
            !address.set_ip_address(_ip) || // Return NULL if the address is not a valid one
            !address) { // Return null if the address is zero
            free (_ip);
            _ip = NULL;
        }
#endif
    }

    UNLOCK();
    return _ip;
}

// Get the IP address of the on-board modem IP stack.
nsapi_error_t UbloxATCellularInterface::get_ip_address(SocketAddress *address)
{
    address->set_ip_address(this->get_ip_address());

    return NSAPI_ERROR_OK;
}

nsapi_error_t UbloxATCellularInterface::get_netmask(SocketAddress *address)
{
    address->set_ip_address("");

    return NSAPI_ERROR_OK;
}

nsapi_error_t UbloxATCellularInterface::get_gateway(SocketAddress *address)
{
    address->set_ip_address(this->get_ip_address());

    return NSAPI_ERROR_OK;
}

// Get the local network mask.
const char *UbloxATCellularInterface::get_netmask()
{
    // Not implemented.
    return NULL;
}

// Get the local gateways.
const char *UbloxATCellularInterface::get_gateway()
{
    return get_ip_address();
}

// Callback in case the connection is lost.
void UbloxATCellularInterface::connection_status_cb(Callback<void(nsapi_error_t)> cb)
{
    _connection_status_cb = cb;
}

void UbloxATCellularInterface::set_plmn(const char *plmn) {

}

// End of file