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