ublox-at-cellular-interface
Revision 0:7ccf0e7e8a83, committed 2017-06-12
- Comitter:
- RobMeades
- Date:
- Mon Jun 12 21:32:21 2017 +0000
- Child:
- 1:bc228becc45d
- Commit message:
- Initial commit, not yet compiling but I'm tired of doing everything in the on-line IDE so I need to publish to take the code off-line.
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TESTS/unit_tests/default/main.cpp Mon Jun 12 21:32:21 2017 +0000 @@ -0,0 +1,1070 @@ +#include "ublox_modem_driver/UbloxATCellularInterface.h" +#include "greentea-client/test_env.h" +#include "unity.h" +#include "utest.h" +#include "UDPSocket.h" +#ifdef FEATURE_COMMON_PAL +#include "mbed_trace.h" +#define TRACE_GROUP "TEST" +#else +#define tr_debug(format, ...) debug(format, ## __VA_ARGS__) +#define tr_info(format, ...) debug(format, ## __VA_ARGS__) +#define tr_warn(format, ...) debug(format, ## __VA_ARGS__) +#define tr_error(format, ...) debug(format, ## __VA_ARGS__) +#endif + +using namespace utest::v1; + +// IMPORTANT!!! if you make a change to the tests here you should +// check whether the same change should be made to the tests under +// the GENERIC driver. + +// ---------------------------------------------------------------- +// COMPILE-TIME MACROS +// ---------------------------------------------------------------- + +// These macros can be overridden with an mbed_app.json file and +// contents of the following form: +// +//{ +// "config": { +// "default-pin": { +// "value": "\"1234\"" +// } +//} +// +// See the template_mbed_app.txt in this directory for a fuller example. + +// Whether debug trace is on +#ifndef MBED_CONF_APP_DEBUG_ON +# define MBED_CONF_APP_DEBUG_ON false +#endif + +// Run the SIM change tests, which require the DEFAULT_PIN +// above to be correct for the board on which the test +// is being run (and the SIM PIN to be disabled before tests run). +#ifndef MBED_CONF_APP_RUN_SIM_PIN_CHANGE_TESTS +# define MBED_CONF_APP_RUN_SIM_PIN_CHANGE_TESTS 0 +#endif + +#if MBED_CONF_APP_RUN_SIM_PIN_CHANGE_TESTS +# ifndef MBED_CONF_APP_DEFAULT_PIN +# error "MBED_CONF_APP_DEFAULT_PIN must be defined to run the SIM tests" +# endif +# ifndef MBED_CONF_APP_ALT_PIN +# error "MBED_CONF_APP_ALT_PIN must be defined to run the SIM tests" +# endif +# ifndef MBED_CONF_APP_INCORRECT_PIN +# error "MBED_CONF_APP_INCORRECT_PIN must be defined to run the SIM tests" +# endif +#endif + +// The credentials of the SIM in the board. +#ifndef MBED_CONF_APP_DEFAULT_PIN +// Note: if PIN is enabled on your SIM, or you wish to run the SIM PIN change +// tests, you must define the PIN for your SIM (see note above on using +// mbed_app.json to do so). +# define MBED_CONF_APP_DEFAULT_PIN "0000" +#endif +#ifndef MBED_CONF_APP_APN +# define MBED_CONF_APP_APN NULL +#endif +#ifndef MBED_CONF_APP_USERNAME +# define MBED_CONF_APP_USERNAME NULL +#endif +#ifndef MBED_CONF_APP_PASSWORD +# define MBED_CONF_APP_PASSWORD NULL +#endif + +// Alternate PIN to use during pin change testing +#ifndef MBED_CONF_APP_ALT_PIN +# define MBED_CONF_APP_ALT_PIN "9876" +#endif + +// A PIN that is definitely incorrect +#ifndef MBED_CONF_APP_INCORRECT_PIN +# define MBED_CONF_APP_INCORRECT_PIN "1530" +#endif + +// Servers and ports +#ifdef MBED_CONF_APP_ECHO_SERVER +# ifndef MBED_CONF_APP_ECHO_UDP_PORT +# error "MBED_CONF_APP_ECHO_UDP_PORT (the port on which your echo server echoes UDP packets) must be defined" +# endif +# ifndef MBED_CONF_APP_ECHO_TCP_PORT +# error "MBED_CONF_APP_ECHO_TCP_PORT (the port on which your echo server echoes TCP packets) must be defined" +# endif +#endif + +#ifndef MBED_CONF_APP_NTP_SERVER +# define MBED_CONF_APP_NTP_SERVER "2.pool.ntp.org" +#else +# ifndef MBED_CONF_APP_NTP_PORT +# error "MBED_CONF_APP_NTP_PORT must be defined if MBED_CONF_APP_NTP_SERVER is defined" +# endif +#endif +#ifndef MBED_CONF_APP_NTP_PORT +# define MBED_CONF_APP_NTP_PORT 123 +#endif + +#ifndef MBED_CONF_APP_LOCAL_PORT +# define MBED_CONF_APP_LOCAL_PORT 15 +#endif + +// UDP packet size limit for testing +#ifndef MBED_CONF_APP_UDP_MAX_PACKET_SIZE +# define MBED_CONF_APP_UDP_MAX_PACKET_SIZE 1024 +#endif + +// The maximum size of UDP data fragmented across +// multiple packets +#ifndef MBED_CONF_APP_UDP_MAX_FRAG_PACKET_SIZE +# define MBED_CONF_APP_UDP_MAX_FRAG_PACKET_SIZE 1500 +#endif + +// TCP packet size limit for testing +#ifndef MBED_CONF_APP_MBED_CONF_APP_TCP_MAX_PACKET_SIZE +# define MBED_CONF_APP_TCP_MAX_PACKET_SIZE 1500 +#endif + +// The number of retries for UDP exchanges +#define NUM_UDP_RETRIES 5 + +// How long to wait for stuff to travel in the async echo tests +#define ASYNC_TEST_WAIT_TIME 10000 + +// The maximum number of sockets that can be open at one time +#define MAX_NUM_SOCKETS 7 + +// ---------------------------------------------------------------- +// PRIVATE VARIABLES +// ---------------------------------------------------------------- + +#ifdef FEATURE_COMMON_PAL +// Lock for debug prints +static Mutex mtx; +#endif + +// An instance of the cellular driver +static UbloxATCellularInterface *driver = + new UbloxATCellularInterface(MDMTXD, MDMRXD, + MBED_CONF_UBLOX_CELL_BAUD_RATE, + MBED_CONF_APP_DEBUG_ON); + +// Connection flag +static bool connection_has_gone_down = false; + +// Data to exchange +static const char send_data[] = "_____0000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0100:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0200:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0300:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0400:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0500:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0600:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0700:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0800:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____0900:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1100:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1200:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1300:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1400:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1500:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1600:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1700:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1800:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____1900:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "_____2000:0123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789"; + +// ---------------------------------------------------------------- +// PRIVATE FUNCTIONS +// ---------------------------------------------------------------- + +#ifdef FEATURE_COMMON_PAL +// Locks for debug prints +static void lock() +{ + mtx.lock(); +} + +static void unlock() +{ + mtx.unlock(); +} +#endif + +// Callback in case the connection goes down +static void connection_down_cb(nsapi_error_t err) +{ + connection_has_gone_down = true; +} + +#ifdef MBED_CONF_APP_ECHO_SERVER +// Make sure that size is greater than 0 and no more than limit, +// useful since, when moduloing a very large number number, +// compilers sometimes screw up and produce a small *negative* +// number. Who knew? For example, GCC decided that +// 492318453 (0x1d582ef5) modulo 508 was -47 (0xffffffd1). +static int fix (int size, int limit) +{ + if (size <= 0) { + size = limit / 2; // better than 1 + } else if (size > limit) { + size = limit; + } + + return size; +} + +// Do a UDP socket echo test to a given host of a given packet size +static void do_udp_echo(UDPSocket *sock, SocketAddress *host_address, int size) +{ + bool success = false; + void * recv_data = malloc (size); + SocketAddress sender_address; + TEST_ASSERT(recv_data != NULL); + + // Retry this a few times, don't want to fail due to a flaky link + for (int x = 0; !success && (x < NUM_UDP_RETRIES); x++) { + tr_debug("Echo testing UDP packet size %d byte(s), try %d.", size, x + 1); + if ((sock->sendto(*host_address, (void*) send_data, size) == size) && + (sock->recvfrom(&sender_address, recv_data, size) == size)) { + TEST_ASSERT (memcmp(send_data, recv_data, size) == 0); + TEST_ASSERT (strcmp(sender_address.get_ip_address(), host_address->get_ip_address()) == 0); + TEST_ASSERT (sender_address.get_port() == host_address->get_port()); + success = true; + } + } + TEST_ASSERT (success); + TEST_ASSERT(!connection_has_gone_down); + + free (recv_data); +} + +// The asynchronous callback +static void async_cb(bool *callback_triggered) +{ + + TEST_ASSERT (callback_triggered != NULL); + *callback_triggered = true; +} + +// Do a UDP echo but using the asynchronous driver; we can exchange +// packets longer in size than one UDP packet this way +static void do_udp_echo_async(UDPSocket *sock, SocketAddress *host_address, + int size, bool *callback_triggered) +{ + void * recv_data = malloc (size); + int recv_size = 0; + SocketAddress sender_address; + Timer timer; + int x, y, z; + TEST_ASSERT(recv_data != NULL); + + *callback_triggered = false; + for (y = 0; (recv_size < size) && (y < NUM_UDP_RETRIES); y++) { + tr_debug("Echo testing UDP packet size %d byte(s) async, try %d.", size, y + 1); + recv_size = 0; + // Retry this a few times, don't want to fail due to a flaky link + if (sock->sendto(*host_address, (void *) send_data, size) == size) { + // Wait for all the echoed data to arrive + timer.start(); + while ((recv_size < size) && (timer.read_ms() < ASYNC_TEST_WAIT_TIME)) { + if (*callback_triggered) { + *callback_triggered = false; + x = sock->recvfrom(&sender_address, (char *) recv_data + recv_size, size); + if (x > 0) { + recv_size += x; + } + tr_debug("%d byte(s) echoed back so far, %d to go.", recv_size, size - recv_size); + TEST_ASSERT(strcmp(sender_address.get_ip_address(), host_address->get_ip_address()) == 0); + TEST_ASSERT(sender_address.get_port() == host_address->get_port()); + } + wait_ms(10); + } + timer.stop(); + timer.reset(); + + // If everything arrived back, check it's the same as we sent + if (recv_size == size) { + z = memcmp(send_data, recv_data, size); + if (z != 0) { + tr_debug("WARNING: mismatch, retrying"); + tr_debug("Sent %d, |%*.*s|", size, size, size, send_data); + tr_debug("Rcvd %d, |%*.*s|", size, size, size, (char *) recv_data); + // If things don't match, it could be due to data loss (this is UDP + // you know...), so set recv_size to 0 to cause another try + recv_size = 0; + } + } + } + } + + TEST_ASSERT(recv_size == size); + TEST_ASSERT(!connection_has_gone_down); + + free (recv_data); +} + +// Send an entire TCP data buffer until done +static int sendAll(TCPSocket *sock, const char *data, int size) +{ + int x; + int count = 0; + Timer timer; + + timer.start(); + while ((count < size) && (timer.read_ms() < 10000)) { + x = sock->send(data + count, size - count); + if (x > 0) { + count += x; + tr_debug("%d byte(s) sent, %d left to send.", count, size - count); + } + wait_ms(10); + } + timer.stop(); + + return count; +} + +// Do a TCP echo but using the asynchronous driver +static void do_tcp_echo_async(TCPSocket *sock, int size, bool *callback_triggered) +{ + void * recv_data = malloc (size); + int recv_size = 0; + int x, y; + Timer timer; + TEST_ASSERT(recv_data != NULL); + + *callback_triggered = false; + tr_debug("Echo testing TCP packet size %d byte(s) async.", size); + TEST_ASSERT (sendAll(sock, send_data, size) == size); + + // Wait for all the echoed data to arrive + timer.start(); + while ((recv_size < size) && (timer.read_ms() < ASYNC_TEST_WAIT_TIME)) { + if (*callback_triggered) { + *callback_triggered = false; + x = sock->recv((char *) recv_data + recv_size, size); + TEST_ASSERT(x > 0); + recv_size += x; + tr_debug("%d byte(s) echoed back so far, %d to go.", recv_size, size - recv_size); + } + wait_ms(10); + } + TEST_ASSERT(recv_size == size); + y = memcmp(send_data, recv_data, size); + if (y != 0) { + tr_debug("Sent %d, |%*.*s|", size, size, size, send_data); + tr_debug("Rcvd %d, |%*.*s|", size, size, size, (char *) recv_data); + TEST_ASSERT(false); + } + timer.stop(); + + TEST_ASSERT(!connection_has_gone_down); + + free (recv_data); +} +#endif + +// Get NTP time from a socket +static void do_ntp_sock (UDPSocket *sock, SocketAddress ntp_address) +{ + char ntp_values[48] = { 0 }; + time_t timestamp = 0; + struct tm *localTime; + char timeString[25]; + time_t TIME1970 = 2208988800U; + int len; + bool comms_done = false; + + ntp_values[0] = '\x1b'; + + // Retry this a few times, don't want to fail due to a flaky link + for (unsigned int x = 0; !comms_done && (x < NUM_UDP_RETRIES); x++) { + sock->sendto(ntp_address, (void*) ntp_values, sizeof(ntp_values)); + len = sock->recvfrom(&ntp_address, (void*) ntp_values, sizeof(ntp_values)); + if (len > 0) { + comms_done = true; + } + } + TEST_ASSERT (comms_done); + + tr_debug("UDP: %d byte(s) returned by NTP server.", len); + if (len >= 43) { + timestamp |= ((int) *(ntp_values + 40)) << 24; + timestamp |= ((int) *(ntp_values + 41)) << 16; + timestamp |= ((int) *(ntp_values + 42)) << 8; + timestamp |= ((int) *(ntp_values + 43)); + timestamp -= TIME1970; + srand (timestamp); + tr_debug("srand() called"); + localTime = localtime(×tamp); + if (localTime) { + if (strftime(timeString, sizeof(timeString), "%a %b %d %H:%M:%S %Y", localTime) > 0) { + printf("NTP timestamp is %s.\n", timeString); + } + } + } +} + +// Get NTP time +static void do_ntp(UbloxATCellularInterface *driver) +{ + UDPSocket sock; + SocketAddress host_address; + + TEST_ASSERT(sock.open(driver) == 0) + + TEST_ASSERT(driver->gethostbyname(MBED_CONF_APP_NTP_SERVER, &host_address) == 0); + host_address.set_port(MBED_CONF_APP_NTP_PORT); + + tr_debug("UDP: NIST server %s address: %s on port %d.", MBED_CONF_APP_NTP_SERVER, + host_address.get_ip_address(), host_address.get_port()); + + sock.set_timeout(10000); + + do_ntp_sock(&sock, host_address); + + sock.close(); +} + +// Use a connection, checking that it is good +static void use_connection(UbloxATCellularInterface *driver) +{ + const char * ip_address = driver->get_ip_address(); + const char * net_mask = driver->get_netmask(); + const char * gateway = driver->get_gateway(); + + TEST_ASSERT(driver->is_connected()); + + TEST_ASSERT(ip_address != NULL); + tr_debug ("IP address %s.", ip_address); + TEST_ASSERT(net_mask == NULL); + tr_debug ("Net mask %s.", net_mask); + TEST_ASSERT(gateway != NULL); + tr_debug ("Gateway %s.", gateway); + + do_ntp(driver); + TEST_ASSERT(!connection_has_gone_down); +} + +// Drop a connection and check that it has dropped +static void drop_connection(UbloxATCellularInterface *driver) +{ + TEST_ASSERT(driver->disconnect() == 0); + TEST_ASSERT(connection_has_gone_down); + connection_has_gone_down = false; + TEST_ASSERT(!driver->is_connected()); +} + +// ---------------------------------------------------------------- +// TESTS +// ---------------------------------------------------------------- + +// Call srand() using the NTP server +void test_set_randomise() { + UDPSocket sock; + SocketAddress host_address; + + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + do_ntp(driver); + TEST_ASSERT(!connection_has_gone_down); + drop_connection(driver); +} + +#ifdef MBED_CONF_APP_ECHO_SERVER + +// Test UDP data exchange +void test_udp_echo() { + UDPSocket sock; + SocketAddress host_address; + SocketAddress local_address; + int x; + int size; + + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + + TEST_ASSERT(driver->gethostbyname(MBED_CONF_APP_ECHO_SERVER, &host_address) == 0); + host_address.set_port(MBED_CONF_APP_ECHO_UDP_PORT); + + tr_debug("UDP: Server %s address: %s on port %d.", MBED_CONF_APP_ECHO_SERVER, + host_address.get_ip_address(), host_address.get_port()); + + TEST_ASSERT(sock.open(driver) == 0) + + // Do a bind, just for the helluvit + local_address.set_port(MBED_CONF_APP_LOCAL_PORT); + TEST_ASSERT(sock.bind(local_address) == 0); + + sock.set_timeout(10000); + + // Test min, max, and some random sizes in-between + do_udp_echo(&sock, &host_address, 1); + do_udp_echo(&sock, &host_address, MBED_CONF_APP_UDP_MAX_PACKET_SIZE); + for (x = 0; x < 10; x++) { + size = (rand() % MBED_CONF_APP_UDP_MAX_PACKET_SIZE) + 1; + size = fix(size, MBED_CONF_APP_UDP_MAX_PACKET_SIZE); + do_udp_echo(&sock, &host_address, size); + } + + sock.close(); + drop_connection(driver); + tr_debug("%d UDP packets of size up to %d byte(s) echoed successfully.", + x, MBED_CONF_APP_UDP_MAX_PACKET_SIZE); +} + +// Test many different sizes of UDP data arriving at once +void test_udp_echo_recv_sizes() { + UDPSocket sock; + SocketAddress host_address; + int x, y, z; + int size; + int tries = 0; + unsigned int offset; + char * recv_data; + bool packetLoss; + bool sendSuccess; + Timer timer; + + driver->deinit(); + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + + TEST_ASSERT(driver->gethostbyname(MBED_CONF_APP_ECHO_SERVER, &host_address) == 0); + host_address.set_port(MBED_CONF_APP_ECHO_UDP_PORT); + + tr_debug("UDP: Server %s address: %s on port %d.", MBED_CONF_APP_ECHO_SERVER, + host_address.get_ip_address(), host_address.get_port()); + + TEST_ASSERT(sock.open(driver) == 0) + + do { + tr_debug("--- UDP packet size test, test try %d, flushing input buffers", tries + 1); + // First of all, clear any junk from the socket + sock.set_timeout(1000); + recv_data = (char *) malloc (MBED_CONF_APP_UDP_MAX_PACKET_SIZE); + TEST_ASSERT(recv_data != NULL); + while (sock.recvfrom(&host_address, (void *) recv_data, MBED_CONF_APP_UDP_MAX_PACKET_SIZE) > 0) { + // Throw it away + } + free (recv_data); + + sock.set_timeout(10000); + + // Throw random sized UDP packets up... + x = 0; + offset = 0; + while (offset < sizeof (send_data)) { + size = (rand() % (MBED_CONF_APP_UDP_MAX_PACKET_SIZE / 2)) + 1; + size = fix(size, MBED_CONF_APP_UDP_MAX_PACKET_SIZE / 2); + if (offset + size > sizeof (send_data)) { + size = sizeof (send_data) - offset; + } + sendSuccess = false; + for (y = 0; !sendSuccess && (y < NUM_UDP_RETRIES); y++) { + tr_debug("Sending UDP packet number %d, size %d byte(s), send try %d.", x + 1, size, y + 1); + if (sock.sendto(host_address, (void *) (send_data + offset), size) == size) { + sendSuccess = true; + offset += size; + } + } + TEST_ASSERT(sendSuccess); + x++; + } + tr_debug("--- All UDP packets sent"); + + // ...and capture them all again afterwards + recv_data = (char *) malloc (sizeof (send_data)); + TEST_ASSERT(recv_data != NULL); + memset (recv_data, 0, sizeof (send_data)); + size = 0; + y = 0; + packetLoss = false; + timer.start(); + while ((size < (int) sizeof (send_data)) && (timer.read_ms() < 10000)) { + y = sock.recvfrom(&host_address, (void *) (recv_data + size), sizeof (send_data) - size); + if (y > 0) { + size += y; + } + } + timer.stop(); + timer.reset(); + tr_debug( "--- Either received everything back or timed out waiting"); + + // Check that we reassembled everything correctly + if (size == sizeof (send_data)) { + for (x = 0; ((*(recv_data + x) == *(send_data + x))) && (x < (int) sizeof (send_data)); x++) { + } + if (x != sizeof (send_data)) { + y = x - 5; + if (y < 0) { + y = 0; + } + z = 10; + if (y + z > (int) sizeof (send_data)) { + z = sizeof(send_data) - y; + } + tr_debug(" --- Difference at character %d (send \"%*.*s\", recv \"%*.*s\")", + x + 1, z, z, send_data + y, z, z, recv_data + y); + packetLoss = true; + } + } else { + tr_debug(" --- %d bytes missing (%d bytes received when %d were expected))", + sizeof (send_data) - size, size, sizeof (send_data)); + packetLoss = true; + } + free (recv_data); + tries++; + } while (packetLoss && (tries < NUM_UDP_RETRIES)); + + TEST_ASSERT(!packetLoss); + TEST_ASSERT(!connection_has_gone_down); + sock.close(); + drop_connection(driver); +} + +// Test UDP data exchange via the asynchronous sigio() mechanism +void test_udp_echo_async() { + UDPSocket sock; + SocketAddress host_address; + SocketAddress local_address; + bool callback_triggered = false; + int x; + int size; + + driver->deinit(); + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + + TEST_ASSERT(driver->gethostbyname(MBED_CONF_APP_ECHO_SERVER, &host_address) == 0); + host_address.set_port(MBED_CONF_APP_ECHO_UDP_PORT); + + tr_debug("UDP: Server %s address: %s on port %d.", MBED_CONF_APP_ECHO_SERVER, + host_address.get_ip_address(), host_address.get_port()); + + TEST_ASSERT(sock.open(driver) == 0) + + // Set up the async callback and set the timeout to zero + sock.sigio(callback(async_cb, &callback_triggered)); + sock.set_timeout(0); + + // Test min, max, and some random sizes in-between + // and this time allow the UDP packets to be fragmented + do_udp_echo_async(&sock, &host_address, 1, &callback_triggered); + do_udp_echo_async(&sock, &host_address, MBED_CONF_APP_UDP_MAX_FRAG_PACKET_SIZE, + &callback_triggered); + for (x = 0; x < 10; x++) { + size = (rand() % MBED_CONF_APP_UDP_MAX_FRAG_PACKET_SIZE) + 1; + size = fix(size, MBED_CONF_APP_UDP_MAX_FRAG_PACKET_SIZE); + do_udp_echo_async(&sock, &host_address, size, &callback_triggered); + } + + sock.close(); + + drop_connection(driver); + + tr_debug("%d UDP packets of size up to %d byte(s) echoed asynchronously and successfully.", + x, MBED_CONF_APP_UDP_MAX_FRAG_PACKET_SIZE); +} + +// Test many different sizes of TCP data arriving at once +void test_tcp_echo_recv_sizes() { + TCPSocket sock; + SocketAddress host_address; + int x, y, z; + int size; + unsigned int offset; + char * recv_data; + Timer timer; + + driver->deinit(); + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + + TEST_ASSERT(driver->gethostbyname(MBED_CONF_APP_ECHO_SERVER, &host_address) == 0); + host_address.set_port(MBED_CONF_APP_ECHO_TCP_PORT); + + tr_debug("TCP: Server %s address: %s on port %d.", MBED_CONF_APP_ECHO_SERVER, + host_address.get_ip_address(), host_address.get_port()); + + TEST_ASSERT(sock.open(driver) == 0) + + TEST_ASSERT(sock.connect(host_address) == 0); + + sock.set_timeout(10000); + + // Throw random sized TCP packets up... + x = 0; + offset = 0; + while (offset < sizeof (send_data)) { + size = (rand() % (MBED_CONF_APP_UDP_MAX_PACKET_SIZE / 2)) + 1; + size = fix(size, MBED_CONF_APP_UDP_MAX_PACKET_SIZE / 2); + if (offset + size > sizeof (send_data)) { + size = sizeof (send_data) - offset; + } + tr_debug("Sending TCP packet number %d, size %d byte(s).", x + 1, size); + TEST_ASSERT(sendAll(&sock, (send_data + offset), size) == size); + offset += size; + x++; + } + + // ...and capture them all again afterwards + recv_data = (char *) malloc (sizeof (send_data)); + TEST_ASSERT(recv_data != NULL); + memset (recv_data, 0, sizeof (send_data)); + size = 0; + x = 0; + timer.start(); + while ((size < (int) sizeof (send_data)) && (timer.read_ms() < 30000)) { + y = sock.recv((void *) (recv_data + size), sizeof (send_data) - size); + tr_debug("Received TCP packet number %d, size %d byte(s).", x, y); + size += y; + x++; + } + timer.stop(); + timer.reset(); + + // Check that we reassembled everything correctly + for (x = 0; ((*(recv_data + x) == *(send_data + x))) && (x < (int) sizeof (send_data)); x++) { + } + if (x != sizeof (send_data)) { + y = x - 5; + if (y < 0) { + y = 0; + } + z = 10; + if (y + z > (int) sizeof (send_data)) { + z = sizeof(send_data) - y; + } + tr_debug("Difference at character %d (send \"%*.*s\", recv \"%*.*s\")", + x + 1, z, z, send_data + y, z, z, recv_data + y); + TEST_ASSERT(false); + } + free (recv_data); + + TEST_ASSERT(!connection_has_gone_down); + sock.close(); + drop_connection(driver); +} + +// Test TCP data exchange via the asynchronous sigio() mechanism +void test_tcp_echo_async() { + TCPSocket sock; + SocketAddress host_address; + bool callback_triggered = false; + int x; + int size; + + driver->deinit(); + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + + TEST_ASSERT(driver->gethostbyname(MBED_CONF_APP_ECHO_SERVER, &host_address) == 0); + host_address.set_port(MBED_CONF_APP_ECHO_TCP_PORT); + + tr_debug("TCP: Server %s address: %s on port %d.", MBED_CONF_APP_ECHO_SERVER, + host_address.get_ip_address(), host_address.get_port()); + + TEST_ASSERT(sock.open(driver) == 0) + + // Set up the async callback and set the timeout to zero + sock.sigio(callback(async_cb, &callback_triggered)); + sock.set_timeout(0); + + TEST_ASSERT(sock.connect(host_address) == 0); + // Test min, max, and some random sizes in-between + do_tcp_echo_async(&sock, 1, &callback_triggered); + do_tcp_echo_async(&sock, MBED_CONF_APP_TCP_MAX_PACKET_SIZE, &callback_triggered); + for (x = 0; x < 10; x++) { + size = (rand() % MBED_CONF_APP_TCP_MAX_PACKET_SIZE) + 1; + size = fix(size, MBED_CONF_APP_TCP_MAX_PACKET_SIZE); + do_tcp_echo_async(&sock, size, &callback_triggered); + } + + sock.close(); + + drop_connection(driver); + + tr_debug("%d TCP packets of size up to %d byte(s) echoed asynchronously and successfully.", + x, MBED_CONF_APP_TCP_MAX_PACKET_SIZE); +} +#endif + +// Allocate max sockets +void test_max_sockets() { + UDPSocket sock[MAX_NUM_SOCKETS]; + UDPSocket sockNone; + SocketAddress host_address; + + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + + TEST_ASSERT(driver->gethostbyname(MBED_CONF_APP_NTP_SERVER, &host_address) == 0); + host_address.set_port(MBED_CONF_APP_NTP_PORT); + + // Open the first socket and use it + TEST_ASSERT(sock[0].open(driver) == 0) + sock[0].set_timeout(10000); + do_ntp_sock(&sock[0], host_address); + + // Check that we stop being able to get sockets at the max number + for (int x = 1; x < (int) (sizeof (sock) / sizeof (sock[0])); x++) { + TEST_ASSERT(sock[x].open(driver) == 0) + } + TEST_ASSERT(sockNone.open(driver) < 0); + + // Now use the last one + sock[sizeof (sock) / sizeof (sock[0]) - 1].set_timeout(10000); + do_ntp_sock(&sock[sizeof (sock) / sizeof (sock[0]) - 1], host_address); + + // Close all of the sockets + for (int x = 0; x < (int) (sizeof (sock) / sizeof (sock[0])); x++) { + TEST_ASSERT(sock[x].close() == 0); + } + + drop_connection(driver); +} + +// Connect with credentials included in the connect request +void test_connect_credentials() { + + driver->deinit(); + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + use_connection(driver); + drop_connection(driver); +} + +// Test with credentials preset +void test_connect_preset_credentials() { + + driver->deinit(); + TEST_ASSERT(driver->init(MBED_CONF_APP_DEFAULT_PIN)); + driver->set_credentials(MBED_CONF_APP_APN, MBED_CONF_APP_USERNAME, + MBED_CONF_APP_PASSWORD); + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN) == 0); + use_connection(driver); + drop_connection(driver); +} + +// Test adding and using a SIM pin, then removing it, using the pending +// mechanism where the change doesn't occur until connect() is called +void test_check_sim_pin_pending() { + + driver->deinit(); + + // Enable PIN checking (which will use the current PIN) + // and also flag that the PIN should be changed to MBED_CONF_APP_ALT_PIN, + // then try connecting + driver->set_sim_pin_check(true); + driver->set_new_sim_pin(MBED_CONF_APP_ALT_PIN); + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + use_connection(driver); + drop_connection(driver); + driver->deinit(); + + // Now change the PIN back to what it was before + driver->set_new_sim_pin(MBED_CONF_APP_DEFAULT_PIN); + TEST_ASSERT(driver->connect(MBED_CONF_APP_ALT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + use_connection(driver); + drop_connection(driver); + driver->deinit(); + + // Check that it was changed back, and this time + // use the other way of entering the PIN + driver->set_sim_pin(MBED_CONF_APP_DEFAULT_PIN); + TEST_ASSERT(driver->connect(NULL, MBED_CONF_APP_APN, MBED_CONF_APP_USERNAME, + MBED_CONF_APP_PASSWORD) == 0); + use_connection(driver); + drop_connection(driver); + driver->deinit(); + + // Remove PIN checking again and check that it no + // longer matters what the PIN is + driver->set_sim_pin_check(false); + TEST_ASSERT(driver->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + use_connection(driver); + drop_connection(driver); + driver->deinit(); + TEST_ASSERT(driver->init(NULL)); + TEST_ASSERT(driver->connect(MBED_CONF_APP_INCORRECT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + use_connection(driver); + drop_connection(driver); + + // Put the SIM pin back to the correct value for any subsequent tests + driver->set_sim_pin(MBED_CONF_APP_DEFAULT_PIN); +} + +// Test adding and using a SIM pin, then removing it, using the immediate +// mechanism +void test_check_sim_pin_immediate() { + + driver->deinit(); + driver->connection_status_cb(connection_down_cb); + + // Enable PIN checking (which will use the current PIN), change + // the PIN to MBED_CONF_APP_ALT_PIN, then try connecting after powering on and + // off the modem + driver->set_sim_pin_check(true, true, MBED_CONF_APP_DEFAULT_PIN); + driver->set_new_sim_pin(MBED_CONF_APP_ALT_PIN, true); + driver->deinit(); + TEST_ASSERT(driver->init(NULL)); + TEST_ASSERT(driver->connect(MBED_CONF_APP_ALT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + use_connection(driver); + drop_connection(driver); + + driver->connection_status_cb(connection_down_cb); + + // Now change the PIN back to what it was before + driver->set_new_sim_pin(MBED_CONF_APP_DEFAULT_PIN, true); + driver->deinit(); + driver->set_sim_pin(MBED_CONF_APP_DEFAULT_PIN); + TEST_ASSERT(driver->init(NULL)); + TEST_ASSERT(driver->connect(NULL, MBED_CONF_APP_APN, MBED_CONF_APP_USERNAME, + MBED_CONF_APP_PASSWORD) == 0); + use_connection(driver); + drop_connection(driver); + + driver->connection_status_cb(connection_down_cb); + + // Remove PIN checking again and check that it no + // longer matters what the PIN is + driver->set_sim_pin_check(false, true); + driver->deinit(); + TEST_ASSERT(driver->init(MBED_CONF_APP_INCORRECT_PIN)); + TEST_ASSERT(driver->connect(NULL, MBED_CONF_APP_APN, MBED_CONF_APP_USERNAME, + MBED_CONF_APP_PASSWORD) == 0); + use_connection(driver); + drop_connection(driver); + + // Put the SIM pin back to the correct value for any subsequent tests + driver->set_sim_pin(MBED_CONF_APP_DEFAULT_PIN); +} + +// Test being able to connect with a local instance of the driver +// NOTE: since this local instance will fiddle with bits of HW that the +// static instance thought it owned, the static instance will no longer +// work afterwards, hence this must be run as the last test in the list +void test_connect_local_instance_last_test() { + + UbloxATCellularInterface *pLocalInterface = NULL; + + pLocalInterface = new UbloxATCellularInterface(MDMTXD, MDMRXD, + MBED_CONF_UBLOX_CELL_BAUD_RATE, + MBED_CONF_APP_DEBUG_ON); + pLocalInterface->connection_status_cb(connection_down_cb); + + TEST_ASSERT(pLocalInterface->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + use_connection(pLocalInterface); + drop_connection(pLocalInterface); + delete pLocalInterface; + + pLocalInterface = new UbloxATCellularInterface(MDMTXD, MDMRXD, + MBED_CONF_UBLOX_CELL_BAUD_RATE, + MBED_CONF_APP_DEBUG_ON); + pLocalInterface->connection_status_cb(connection_down_cb); + + TEST_ASSERT(pLocalInterface->connect(MBED_CONF_APP_DEFAULT_PIN, MBED_CONF_APP_APN, + MBED_CONF_APP_USERNAME, MBED_CONF_APP_PASSWORD) == 0); + use_connection(pLocalInterface); + drop_connection(pLocalInterface); + delete pLocalInterface; +} + +// ---------------------------------------------------------------- +// TEST ENVIRONMENT +// ---------------------------------------------------------------- + +// Setup the test environment +utest::v1::status_t test_setup(const size_t number_of_cases) { + // Setup Greentea with a timeout + GREENTEA_SETUP(960, "default_auto"); + return verbose_test_setup_handler(number_of_cases); +} + +// IMPORTANT!!! if you make a change to the tests here you should +// check whether the same change should be made to the tests under +// the GENERIC driver. + +// Test cases +Case cases[] = { + Case("Set randomise", test_set_randomise), +#ifdef MBED_CONF_APP_ECHO_SERVER + Case("UDP echo test", test_udp_echo), +# ifndef TARGET_UBLOX_C027 // Not enough RAM on little 'ole C027 to run this test + Case("UDP recv sizes", test_udp_echo_recv_sizes), +# endif + Case("UDP async echo test", test_udp_echo_async), +# ifndef TARGET_UBLOX_C027 // Not enough RAM on little 'ole C027 to run this test + Case("TCP recv sizes", test_tcp_echo_recv_sizes), +# endif + Case("TCP async echo test", test_tcp_echo_async), +#endif +#ifndef TARGET_UBLOX_C027 // Not enough RAM on little 'ole C027 to run this test + Case("Alloc max sockets", test_max_sockets), +#endif + Case("Connect with credentials", test_connect_credentials), + Case("Connect with preset credentials", test_connect_preset_credentials), +#if MBED_CONF_APP_RUN_SIM_PIN_CHANGE_TESTS + Case("Check SIM pin, pending", test_check_sim_pin_pending), + Case("Check SIM pin, immediate", test_check_sim_pin_immediate), +#endif +#ifndef TARGET_UBLOX_C027 // Not enough RAM on little 'ole C027 for this + Case("Connect using local instance, must be last test", test_connect_local_instance_last_test) +#endif +}; + +Specification specification(test_setup, cases); + +// ---------------------------------------------------------------- +// MAIN +// ---------------------------------------------------------------- + +int main() { + +#ifdef FEATURE_COMMON_PAL + mbed_trace_init(); + + mbed_trace_mutex_wait_function_set(lock); + mbed_trace_mutex_release_function_set(unlock); +#endif + + driver->connection_status_cb(connection_down_cb); + + // Run tests + return !Harness::run(specification); +} + +// End Of File +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TESTS/unit_tests/default/template_mbed_app.txt Mon Jun 12 21:32:21 2017 +0000 @@ -0,0 +1,84 @@ +{ + "config": { + "platform": { + "help": "The platform for the cellular feature, e.g. UBLOX or MTS_DRAGONFLY", + "value": "UBLOX" + }, + "buffer-size": { + "value": 0 + }, + "debug-on": { + "help": "Set to true to get AT interface debug", + "value": false + }, + "run-sim-pin-change-tests": { + "help": "If 1, run the SIM PIN change tests, for which default-pin must be defined", + "value": 0 + }, + "default-pin": { + "help": "The current value of the SIM PIN as a string; if PIN is enabled on your SIM, or you wish to run the SIM PIN change tests, you must put the PIN for your SIM here", + "value": "\"1234\"" + }, + "apn": { + "help": "The APN string to use for this SIM/network, set to 0 if none", + "value": 0 + }, + "username": { + "help": "The user name string to use for this APN, set to zero if none", + "value": 0 + }, + "password": { + "help": "The password string to use for this APN, set to 0 if none", + "value": 0 + }, + "alt-pin": { + "help": "The SIM PIN (as a string) that will be used for SIM PIN change tests (the SIM PIN will be changed back to default-pin afterwards)", + "value": "\"9876\"" + }, + "incorrect-pin": { + "help": "A SIM PIN (as a string) that should not be the same as default-pin or alt-pin", + "value": "\"1530\"" + }, + "echo-server": { + "help": "The URL string of the UDP/TCP echo server to use during testing; if this is not defined, no echo tests will be run (and it is REALLY recommended to run them for this driver)", + "value": "\"yourechoserver.com\"" + }, + "echo-udp-port": { + "help": "The port to connect to on echo-server for UDP testing", + "value": 7 + }, + "echo-tcp-port": { + "help": "The port to connect to on echo-server for TCP testing", + "value": 7 + }, + "ntp-server": { + "help": "The URL string of the NTP server to use during testing", + "value": "\"2.pool.ntp.org\"" + }, + "ntp-port": { + "help": "The port to connect to on ntp-server", + "value": 123 + }, + "local-port": { + "help": "The local port to use when testing sock.bind()", + "value": 16 + }, + "udp-max-packet-size": { + "help": "The maximum UDP packet size to use when testing; 1024 bytes is the limit at the AT interface but 508 bytes is considered more reliable for the public internet", + "value": 508 + } + "udp-max-frag-packet-size": { + "help": "The maximum size of UDP data to test with that we know will be fragmented across multiple UDP packets", + "value": 1500 + } + }, + "target_overrides": { + "*": { + "target.features_add": ["COMMON_PAL"], + "platform.stdio-convert-newlines": true, + "platform.stdio-baud-rate": 9600, + "platform.default-serial-baud-rate": 115200, + "mbed-trace.enable": 1 + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UbloxATCellularInterface.cpp Mon Jun 12 21:32:21 2017 +0000 @@ -0,0 +1,1208 @@ +/* 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(format, ## __VA_ARGS__) +#define tr_info(format, ...) debug(format, ## __VA_ARGS__) +#define tr_warn(format, ...) debug(format, ## __VA_ARGS__) +#define tr_error(format, ...) debug(format, ## __VA_ARGS__) +#endif + +/********************************************************************** + * PRIVATE METHODS + **********************************************************************/ + +// Event thread for asynchronous received data handling. +void UbloxATCellularInterface::handle_event(){ + pollfh fhs; + int count; + int at_timeout; + + fhs.fh = _fh; + fhs.events = POLLIN; + + while (true) { + 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; + case NSAPI_SECURITY_UNKNOWN: + modem_security = 3; + break; + default: + modem_security = 3; + break; + } + + return modem_security; +} + +// Callback for Socket Read URC. +void UbloxATCellularInterface::UUSORD_URC() +{ + int a; + int b; + char buf[32]; + SockCtrl *socket; + + // 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) { + 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]; + SockCtrl *socket; + + // 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) { + 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]; + SockCtrl *socket; + + // 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) { + 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]; + SockCtrl *socket; + + // 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) { + 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 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")) { + // Activate, waiting 30 seconds for the connection to be made + at_set_timeout(30000); + activated = _at->send("AT+UPSDA=" PROFILE ",3") && _at->recv("OK"); + at_set_timeout(at_timeout); + } + } + } + } + + 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; + char ip[NSAPI_IP_SIZE]; + 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?")) { + 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; + 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); + } + + return success; +} + +// 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; + 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); + } + } + } + + 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; + bool success = false; + int modem_handle; + SockCtrl *socket; + LOCK(); + + // Find a free socket + socket = find_socket(); + tr_debug("socket_open(%d)", proto); + + if (socket != NULL) { + 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)); + + while ((count > 0) && success) { + if (count < blk) { + blk = count; + } + LOCK(); + + if (_at->send("AT+USOWR=%d,%d", socket->modem_handle, blk) && _at->recv("@")) { + wait_ms(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("@")) { + wait_ms(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; + int at_timeout; + + tr_debug("socket_recv(0x%08x, 0x%08x, %d)", + (unsigned int) handle, (unsigned int) data, size); + + MBED_ASSERT (check_socket(socket)); + + timer.start(); + + while (success && (size > 0)) { + 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)) { + socket->pending -= usord_sz; // Must use what +USORD returns here as it + // may be less than we asked for + // 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; + int at_timeout; + + 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)) { + 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)) { + socket->pending -= usorf_sz; // Must use what +USORF returns here as it + // may be less than we asked for + // 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) +{ + _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; + _apn = NULL; + _uname = NULL; + _pwd = NULL; + _connection_status_cb = 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; + + // 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)); + + // 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() +{ + // 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; + char ipAddress[NSAPI_IP_SIZE]; + + if (address->set_ip_address(host)) { + nsapi_error = NSAPI_ERROR_OK; + } else { + LOCK(); + 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; + } + } + 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; + } + + if (nsapi_error == NSAPI_ERROR_NO_CONNECTION) { + for (int retries = 0; !registered && (retries < 3); retries++) { + if (nwk_registration()) { + registered = true;; + } + } + } + } + + // Attempt to establish a connection + if (registered && connect_modem_stack()) { + nsapi_error = NSAPI_ERROR_OK; + } + + return nsapi_error; +} + +// User initiated disconnect. +nsapi_error_t UbloxATCellularInterface::disconnect() +{ + nsapi_error_t nsapi_error = NSAPI_ERROR_DEVICE_ERROR; + + if (disconnect_modem_stack() && nwk_deregistration()) { + 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. + 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; + } + } + + UNLOCK(); + return _ip; +} + +// 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; +} + +// End of file +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/UbloxATCellularInterface.h Mon Jun 12 21:32:21 2017 +0000 @@ -0,0 +1,625 @@ +/* Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _UBLOX_CELLULAR_DRIVER_GEN_AT_DATA_ +#define _UBLOX_CELLULAR_DRIVER_GEN_AT_DATA_ + +#include "ublox_modem_driver/UbloxCellularBase.h" +#include "netsocket/CellularBase.h" +#include "NetworkStack.h" + +/** UbloxATCellularInterface class. + * + * This uses the cellular-tuned IP stack that + * is on-board the cellular modem instead of the + * LWIP stack on the mbed MCU. + * + * There are three advantages to using this mechanism: + * + * 1. Since the modem interface remains in AT mode + * throughout, it is possible to continue using + * any AT commands (e.g. send SMS, use the module's + * file system, etc.) while the connection is up. + * + * 2. The UbloxATCellularInterfaceExt class can + * be used to perform very simple HTTP and FTP + * operations using the modem's on-board clients. + * + * 3. LWIP is not required (and hence RAM is saved). + * + * The disadvantage is that some additional parsing + * (at the AT interface) has to go on in order to exchange + * IP packets, so this is less efficient under heavy loads. + * Also TCP Server and getting/setting of socket options is + * currently not supported. + */ + +// Forward declaration +class NetworkStack; + +/* + * NOTE: order is important in the inheritance below! PAL takes this class + * and casts it to CellularInterface and so CellularInterface has to be first + * in the last for that to work. + */ + +/** UbloxATCellularInterface class. + * + * This class implements the network stack interface into the cellular + * modems on the C030 and C027 boards for 2G/3G/4G modules using + * the IP stack running on the cellular module. + */ +class UbloxATCellularInterface : public CellularBase, public NetworkStack, virtual public UbloxCellularBase { + +public: + /** Constructor. + * + * @param tx the UART TX data pin to which the modem is attached. + * @param rx the UART RX data pin to which the modem is attached. + * @param baud the UART baud rate. + * @param debug_on true to switch AT interface debug on, otherwise false. + */ + UbloxATCellularInterface(PinName tx = MDMTXD, + PinName rx = MDMRXD, + int baud = MBED_CONF_UBLOX_CELL_BAUD_RATE, + bool debug_on = false); + + /* Destructor. + */ + virtual ~UbloxATCellularInterface(); + + /** The amount of extra space needed in terms of AT interface + * characters to get a chunk of user data (i.e. one UDP packet + * or a portion of a TCP packet) across the AT interface. + */ + #define AT_PACKET_OVERHEAD 77 + + /** The profile to use (on board the modem). + */ + #define PROFILE "0" + + /** Translates a host name to an IP address with specific IP version. + * + * The host name may be either a domain name or an IP address. If the + * host name is an IP address, no network transactions will be performed. + * + * If no stack-specific DNS resolution is provided, the host name + * will be resolved using a UDP socket on the stack. + * + * @param host Host name to resolve. + * @param address Destination for the host SocketAddress. + * @param version IP version of address to resolve, NSAPI_UNSPEC indicates + * version is chosen by the stack (defaults to NSAPI_UNSPEC). + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t gethostbyname(const char *host, + SocketAddress *address, + nsapi_version_t version = NSAPI_UNSPEC); + + /** Set the authentication scheme. + * + * @param auth The authentication scheme, chose from + * NSAPI_SECURITY_NONE, NSAPI_SECURITY_PAP, + * NSAPI_SECURITY_CHAP or NSAPI_SECURITY_UNKNOWN; + * use NSAPI_SECURITY_UNKNOWN to try all of none, + * PAP and CHAP. + */ + virtual void set_authentication(nsapi_security_t auth); + + /** Set the cellular network credentials. + * + * Please check documentation of connect() for default behaviour of APN settings. + * + * @param apn Access point name. + * @param uname Optionally, user name. + * @param pwd Optionally, password. + */ + virtual void set_credentials(const char *apn, const char *uname = 0, + const char *pwd = 0); + + /** Set the PIN code for the SIM card. + * + * @param sim_pin PIN for the SIM card. + */ + virtual void set_sim_pin(const char *sim_pin); + + /** Connect to the cellular network and start the interface. + * + * Attempts to connect to a cellular network. Note: if init() has + * not been called beforehand, connect() will call it first. + * + * @param sim_pin PIN for the SIM card. + * @param apn Optionally, access point name. + * @param uname Optionally, user name. + * @param pwd Optionally, password. + * @return NSAPI_ERROR_OK on success, or negative error code on failure. + */ + virtual nsapi_error_t connect(const char *sim_pin, const char *apn = 0, + const char *uname = 0, const char *pwd = 0); + + /** Attempt to connect to the cellular network. + * + * Brings up the network interface. Connects to the cellular radio + * network and then brings up IP stack on the cellular modem to be used + * indirectly via AT commands, rather than LWIP. Note: if init() has + * not been called beforehand, connect() will call it first. + * NOTE: even a failed attempt to connect will cause the modem to remain + * powered up. To power it down, call deinit(). + * + * For APN setup, default behaviour is to use 'internet' as APN string + * and assuming no authentication is required, i.e., user name and password + * are not set. Optionally, a database lookup can be requested by turning + * on the APN database lookup feature. The APN database is by no means + * exhaustive (feel free to submit a pull request with additional values). + * It contains a short list of some public APNs with publicly available + * user names and passwords (if required) in some particular countries only. + * Lookup is done using IMSI (International mobile subscriber identifier). + * + * The preferred method is to setup APN using 'set_credentials()' API. + * + * If you find that the AT interface returns "CONNECT" but shortly afterwards + * drops the connection then 99% of the time this will be because the APN + * is incorrect. + * + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t connect(); + + /** Attempt to disconnect from the network. + * + * Brings down the network interface. + * Does not bring down the Radio network. + * + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t disconnect(); + + /** Adds or removes a SIM facility lock. + * + * Can be used to enable or disable SIM PIN check at device startup. + * + * @param set Can be set to true if the SIM PIN check is supposed + * to be enabled and vice versa. + * @param immediate If true, change the SIM PIN now, else set a flag + * and make the change only when connect() is called. + * If this is true and init() has not been called previously, + * it will be called first. + * @param sim_pin The current SIM PIN, must be a const. If this is not + * provided, the SIM PIN must have previously been set by a + * call to set_sim_pin(). + * @return 0 on success, negative error code on failure. + */ + nsapi_error_t set_sim_pin_check(bool set, bool immediate = false, + const char *sim_pin = NULL); + + /** Change the PIN for the SIM card. + * + * Provide the new PIN for your SIM card with this API. It is ONLY possible to + * change the SIM PIN when SIM PIN checking is ENABLED. + * + * @param new_pin New PIN to be used in string format, must be a const. + * @param immediate If true, change the SIM PIN now, else set a flag + * and make the change only when connect() is called. + * If this is true and init() has not been called previously, + * it will be called first. + * @param old_pin Old PIN, must be a const. If this is not provided, the SIM PIN + * must have previously been set by a call to set_sim_pin(). + * @return 0 on success, negative error code on failure. + */ + nsapi_error_t set_new_sim_pin(const char *new_pin, bool immediate = false, + const char *old_pin = NULL); + + /** Check if the connection is currently established or not. + * + * @return True if connected to a data network, otherwise false. + */ + virtual bool is_connected(); + + /** Get the local IP address + * + * @return Null-terminated representation of the local IP address + * or null if no IP address has been received. + */ + virtual const char *get_ip_address(); + + /** Get the local network mask. + * + * @return Null-terminated representation of the local network mask + * or null if no network mask has been received. + */ + virtual const char *get_netmask(); + + /** Get the local gateways. + * + * @return Null-terminated representation of the local gateway + * or null if no network mask has been received. + */ + virtual const char *get_gateway(); + + /** Call back in case connection is lost. + * + * @param cb The function to call. + */ + void connection_status_cb(Callback<void(nsapi_error_t)> cb); + +protected: + + /** Socket "unused" value. + */ + #define SOCKET_UNUSED -1 + + /** Socket timeout value in milliseconds. + * Note: the sockets layer above will retry the + * call to the functions here when they return NSAPI_ERROR_WOULD_BLOCK + * and the user has set a larger timeout or full blocking. + */ + #define SOCKET_TIMEOUT 1000 + + /** The maximum number of bytes in a packet that can be written + * to the AT interface in one go. + */ + #define MAX_WRITE_SIZE 1024 + + /** The maximum number of bytes in a packet that can be read from + * from the AT interface in one go. + */ + #define MAX_READ_SIZE 1024 + + /** Management structure for sockets. + */ + typedef struct { + int modem_handle; //!< The modem's handle for the socket. + volatile nsapi_size_t pending; //!< The number of received bytes pending. + void (*callback)(void *); //!< A callback for events. + void *data; //!< A data pointer that must be passed to the callback. + } SockCtrl; + + /** Sockets storage. + */ + SockCtrl _sockets[7]; + + /** Storage for a single IP address. + */ + char *_ip; + + /** The APN to use. + */ + const char *_apn; + + /** The user name to use. + */ + const char *_uname; + + /** The password to use. + */ + const char *_pwd; + + /** The type of authentication to use. + */ + nsapi_security_t _auth; + + /** Get the next set of credentials from the database. + */ + virtual void get_next_credentials(const char * config); + + /** Activate one of the on-board modem's connection profiles. + * + * @param apn The APN to use. + * @param username The user name to use. + * @param password The password to use. + * @param auth The authentication method to use + * (NSAPI_SECURITY_NONE, NSAPI_SECURITY_PAP, + * NSAPI_SECURITY_CHAP or NSAPI_SECURITY_UNKNOWN). + * @return True if successful, otherwise false. + */ + virtual bool activate_profile(const char* apn, const char* username, + const char* password, nsapi_security_t auth); + + /** Activate a profile using the existing external connection. + * + * @return true if successful, otherwise false. + */ + virtual bool activate_profile_reuse_external(void); + + /** Activate a profile based on connection ID. + * + * @param cid The connection ID. + * @param apn The APN to use. + * @param username The user name to use. + * @param password The password to use. + * @param auth The authentication method to use. + * @return True if successful, otherwise false. + */ + virtual bool activate_profile_by_cid(int cid, const char* apn, const char* username, + const char* password, nsapi_security_t auth); + + /** Connect the on board IP stack of the modem. + * + * @return True if successful, otherwise false. + */ + virtual bool connect_modem_stack(); + + /** Disconnect the on board IP stack of the modem. + * + * @return True if successful, otherwise false. + */ + virtual bool disconnect_modem_stack(); + + /** Provide access to the NetworkStack object + * + * @return The underlying NetworkStack object. + */ + virtual NetworkStack *get_stack(); + +protected: + + /** Open a socket. + * + * Creates a network socket and stores it in the specified handle. + * The handle must be passed to following calls on the socket. + * + * @param handle Destination for the handle to a newly created socket. + * @param proto Protocol of socket to open, NSAPI_TCP or NSAPI_UDP. + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t socket_open(nsapi_socket_t *handle, + nsapi_protocol_t proto); + + /** Close a socket. + * + * Closes any open connection and deallocates any memory associated + * with the socket. + * + * @param handle Socket handle. + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t socket_close(nsapi_socket_t handle); + + /** Bind a specific port to a socket. + * + * Binding a socket specifies port on which to receive + * data. The IP address is ignored. Note that binding + * a socket involves closing it and reopening and so the + * bind operation should be carried out before any others. + * + * @param handle Socket handle. + * @param address Local address to bind (of which only the port is used). + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t socket_bind(nsapi_socket_t handle, + const SocketAddress &address); + + /** Connects TCP socket to a remote host. + * + * Initiates a connection to a remote server specified by the + * indicated address. + * + * @param handle Socket handle. + * @param address The SocketAddress of the remote host. + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t socket_connect(nsapi_socket_t handle, + const SocketAddress &address); + + /** Send data over a TCP socket. + * + * The socket must be connected to a remote host. Returns the number of + * bytes sent from the buffer. This class sets no upper buffer limit on + * buffer size and the maximum packet size is not connected with the + * platform.buffered-serial-txbuf-size/platform.buffered-serial-rxbuf-size + * definitions. + * + * @param handle Socket handle. + * @param data Buffer of data to send to the host. + * @param size Size of the buffer in bytes. + * @return Number of sent bytes on success, negative error + * code on failure. + */ + virtual nsapi_size_or_error_t socket_send(nsapi_socket_t handle, + const void *data, nsapi_size_t size); + + /** Send a packet over a UDP socket. + * + * Sends data to the specified address. Returns the number of bytes + * sent from the buffer. + * + * PACKET SIZES: the maximum packet size that can be sent in a single + * UDP packet is limited by the configuration value + * platform.buffered-serial-txbuf-size (defaults to 256). + * The maximum UDP packet size is: + * + * platform.buffered-serial-txbuf-size - AT_PACKET_OVERHEAD + * + * ...with a limit of 1024 bytes (at the AT interface). So, to allow sending + * of a 1024 byte UDP packet, edit your mbed_app.json to add a target override + * setting platform.buffered-serial-txbuf-size to 1101. However, for + * UDP packets, 508 bytes is considered a more realistic size, taking into + * account fragmentation sizes over the public internet, which leads to a + * platform.buffered-serial-txbuf-size/platform.buffered-serial-rxbuf-size + * setting of 585. + * + * If size is larger than this limit, the data will be split across separate + * UDP packets. + * + * @param handle Socket handle. + * @param address The SocketAddress of the remote host. + * @param data Buffer of data to send to the host. + * @param size Size of the buffer in bytes. + * @return Number of sent bytes on success, negative error + * code on failure. + */ + virtual nsapi_size_or_error_t socket_sendto(nsapi_socket_t handle, + const SocketAddress &address, + const void *data, + nsapi_size_t size); + + /** Receive data over a TCP socket. + * + * The socket must be connected to a remote host. Returns the number of + * bytes received into the buffer. This class sets no upper limit on the + * buffer size and the maximum packet size is not connected with the + * platform.buffered-serial-txbuf-size/platform.buffered-serial-rxbuf-size + * definitions. + * + * @param handle Socket handle. + * @param data Destination buffer for data received from the host. + * @param size Size of the buffer in bytes. + * @return Number of received bytes on success, negative error + * code on failure. + */ + virtual nsapi_size_or_error_t socket_recv(nsapi_socket_t handle, + void *data, nsapi_size_t size); + + /** Receive a packet over a UDP socket. + * + * Receives data and stores the source address in address if address + * is not NULL. Returns the number of bytes received into the buffer. + * + * PACKET SIZES: the maximum packet size that can be retrieved in a + * single call to this method is limited by the configuration value + * platform.buffered-serial-rxbuf-size (default 256). The maximum + * UDP packet size is: + * + * platform.buffered-serial-rxbuf-size - AT_PACKET_OVERHEAD + * + * ...with a limit of 1024 (at the AT interface). So to allow reception of a + * 1024 byte UDP packet in a single call, edit your mbed_app.json to add a + * target override setting platform.buffered-serial-rxbuf-size to 1101. + * + * If the received packet is larger than this limit, any remainder will + * be returned in subsequent calls to this method. Once a single UDP + * packet has been received, this method will return. + * + * @param handle Socket handle. + * @param address Destination for the source address or NULL. + * @param data Destination buffer for data received from the host. + * @param size Size of the buffer in bytes. + * @return Number of received bytes on success, negative error + * code on failure. + */ + virtual nsapi_size_or_error_t socket_recvfrom(nsapi_socket_t handle, + SocketAddress *address, + void *data, nsapi_size_t size); + + /** Register a callback on state change of the socket. + * + * The specified callback will be called on state changes such as when + * the socket can recv/send/accept successfully and on when an error + * occurs. The callback may also be called spuriously without reason. + * + * The callback may be called in an interrupt context and should not + * perform expensive operations such as recv/send calls. + * + * @param handle Socket handle. + * @param callback Function to call on state change. + * @param data Argument to pass to callback. + */ + virtual void socket_attach(nsapi_socket_t handle, void (*callback)(void *), + void *data); + + /** Listen for connections on a TCP socket. + * + * Marks the socket as a passive socket that can be used to accept + * incoming connections. + * + * @param handle Socket handle. + * @param backlog Number of pending connections that can be queued + * simultaneously, defaults to 1. + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t socket_listen(nsapi_socket_t handle, int backlog); + + /** Accepts a connection on a TCP socket. + * + * The server socket must be bound and set to listen for connections. + * On a new connection, creates a network socket and stores it in the + * specified handle. The handle must be passed to following calls on + * the socket. + * + * A stack may have a finite number of sockets, in this case + * NSAPI_ERROR_NO_SOCKET is returned if no socket is available. + * + * This call is non-blocking. If accept would block, + * NSAPI_ERROR_WOULD_BLOCK is returned immediately. + * + * @param server Socket handle to server to accept from. + * @param handle Destination for a handle to the newly created socket. + * @param address Destination for the remote address or NULL. + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t socket_accept(nsapi_socket_t server, + nsapi_socket_t *handle, + SocketAddress *address = 0); + + /** Set stack-specific socket options. + * + * The setsockopt allow an application to pass stack-specific hints + * to the underlying stack. For unsupported options, + * NSAPI_ERROR_UNSUPPORTED is returned and the socket is unmodified. + * + * @param handle Socket handle. + * @param level Stack-specific protocol level. + * @param optname Stack-specific option identifier. + * @param optval Option value. + * @param optlen Length of the option value. + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t setsockopt(nsapi_socket_t handle, int level, + int optname, const void *optval, + unsigned optlen); + + /** Get stack-specific socket options. + * + * The getstackopt allow an application to retrieve stack-specific hints + * from the underlying stack. For unsupported options, + * NSAPI_ERROR_UNSUPPORTED is returned and optval is unmodified. + * + * @param handle Socket handle. + * @param level Stack-specific protocol level. + * @param optname Stack-specific option identifier. + * @param optval Destination for option value. + * @param optlen Length of the option value. + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t getsockopt(nsapi_socket_t handle, int level, + int optname, void *optval, + unsigned *optlen); + +private: + + // u_ added to namespace us somewhat as this darned macro + // is defined by everyone and their dog + #define u_stringify(a) str(a) + #define str(a) #a + + bool _sim_pin_check_change_pending; + bool _sim_pin_check_change_pending_enabled_value; + bool _sim_pin_change_pending; + const char *_sim_pin_change_pending_new_pin_value; + Thread event_thread; + void handle_event(); + SockCtrl * find_socket(int modem_handle = SOCKET_UNUSED); + void clear_socket(SockCtrl * socket); + bool check_socket(SockCtrl * socket); + int nsapi_security_to_modem_security(nsapi_security_t nsapi_security); + Callback<void(nsapi_error_t)> _connection_status_cb; + void UUSORD_URC(); + void UUSORF_URC(); + void UUSOCL_URC(); + void UUPSDD_URC(); +}; + +#endif // _UBLOX_CELLULAR_DRIVER_GEN_AT_DATA_ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ublox-cellular-base.lib Mon Jun 12 21:32:21 2017 +0000 @@ -0,0 +1,1 @@ +https://developer.mbed.org/teams/ublox/code/ublox-cellular-base/#5cffef3371f6