For a PR
Fork of ublox-at-cellular-interface by
Diff: UbloxATCellularInterface.cpp
- Revision:
- 0:7ccf0e7e8a83
- Child:
- 1:bc228becc45d
--- /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 +