Implementation of the CellularInterface for u-blox C030 boards with N2xx modems. Note: requires the N211 module firmware to be at least 06.57 A01.02.
Dependents: example-ublox-cellular-interface HelloMQTT example-ublox-cellular-interface_r410M example-ublox-mbed-client ... more
UbloxATCellularInterfaceN2xx.cpp
- Committer:
- fahim.alavi@u-blox.com
- Date:
- 2019-02-13
- Revision:
- 14:7fb0bbdc4242
- Parent:
- 13:2504d4160042
File content as of revision 14:7fb0bbdc4242:
/* 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 "UbloxATCellularInterfaceN2xx.h" #include "mbed_poll.h" #include "nsapi.h" #include "APN_db.h" #ifdef FEATURE_COMMON_PAL #include "mbed_trace.h" #define TRACE_GROUP "UACI" #else #define tr_debug(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__) #define tr_info(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__) #define tr_warn(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__) #define tr_error(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__) #endif // When calling the SendTo function, the large hex string for the bytes to send is chopped into chunks #define SENDTO_CHUNK_SIZE 50 /********************************************************************** * PRIVATE METHODS **********************************************************************/ // Event thread for asynchronous received data handling. void UbloxATCellularInterfaceN2xx::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. UbloxATCellularInterfaceN2xx::SockCtrl * UbloxATCellularInterfaceN2xx::find_socket(int modem_handle) { UbloxATCellularInterfaceN2xx::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 UbloxATCellularInterfaceN2xx::clear_socket(UbloxATCellularInterfaceN2xx::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 UbloxATCellularInterfaceN2xx::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; } // Callback for Socket Read From URC. void UbloxATCellularInterfaceN2xx::NSONMI_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_newline(buf, sizeof (buf)) > 0) { tr_debug("NSONMI URC"); if (sscanf(buf, ":%d,%d", &a, &b) == 2) { socket = find_socket(a); if (socket != NULL) { socket->pending += b; tr_debug("Socket 0x%08x: modem handle %d has %d byte(s) pending", (unsigned int) socket, a, socket->pending); if (socket->callback != NULL) { tr_debug("***** Calling callback..."); socket->callback(socket->data); tr_debug("***** Callback finished"); } else { tr_debug("No callback found for socket."); } } else { tr_debug("Can't find socket with modem handle %d", a); } } } } /********************************************************************** * PROTECTED METHODS: GENERAL **********************************************************************/ // Get the next set of credentials, based on IMSI. void UbloxATCellularInterfaceN2xx::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 : ""; } /********************************************************************** * PROTECTED METHODS: NETWORK INTERFACE and SOCKETS **********************************************************************/ // Gain access to us. NetworkStack *UbloxATCellularInterfaceN2xx::get_stack() { return this; } // Create a socket. nsapi_error_t UbloxATCellularInterfaceN2xx::socket_open(nsapi_socket_t *handle, nsapi_protocol_t proto) { nsapi_error_t nsapi_error = NSAPI_ERROR_DEVICE_ERROR; int modem_handle =0; SockCtrl *socket; if (proto != NSAPI_UDP) { return NSAPI_ERROR_UNSUPPORTED; } LOCK(); // Find a free socket socket = find_socket(); if (socket != NULL) { tr_debug("socket_open(%d)", proto); if (_at->send("AT+NSOCR=\"DGRAM\",17,%d", _localListenPort) && _at->recv("%d\n", &modem_handle) && _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 { tr_error("Couldn't open socket using AT command"); } } else { tr_error("Can't find a socket to use"); nsapi_error = NSAPI_ERROR_NO_MEMORY; } UNLOCK(); return nsapi_error; } // Close a socket. nsapi_error_t UbloxATCellularInterfaceN2xx::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+NSOCL=%d", socket->modem_handle) && _at->recv("OK")) { clear_socket(socket); nsapi_error = NSAPI_ERROR_OK; } else { tr_error("Failed to close socket %d", socket->modem_handle); } UNLOCK(); return nsapi_error; } // Bind a local port to a socket. nsapi_error_t UbloxATCellularInterfaceN2xx::socket_bind(nsapi_socket_t handle, const SocketAddress &address) { return NSAPI_ERROR_UNSUPPORTED; } // Connect to a socket nsapi_error_t UbloxATCellularInterfaceN2xx::socket_connect(nsapi_socket_t handle, const SocketAddress &address) { return NSAPI_ERROR_UNSUPPORTED; } // Send to a socket. nsapi_size_or_error_t UbloxATCellularInterfaceN2xx::socket_send(nsapi_socket_t handle, const void *data, nsapi_size_t size) { return NSAPI_ERROR_UNSUPPORTED; } // Send to an IP address. nsapi_size_or_error_t UbloxATCellularInterfaceN2xx::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_N2XX; 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)); tr_debug("Max Write Size for SendTo: %d", MAX_WRITE_SIZE_N2XX); if (size > MAX_WRITE_SIZE_N2XX) { tr_warn("WARNING: packet length %d is too big for one UDP packet (max %d), will be fragmented.", size, MAX_WRITE_SIZE_N2XX); } while ((count > 0) && success) { if (count < blk) { blk = count; } // call the AT Helper function to send the bytes tr_debug("Sending %d bytes....", blk); int sent = sendto(socket, address, buf, blk); if (sent < 0) { tr_error("Something went wrong! %d", sent); return NSAPI_ERROR_DEVICE_ERROR; } buf += sent; count -= sent; } 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; } nsapi_size_or_error_t UbloxATCellularInterfaceN2xx::sendto(SockCtrl *socket, const SocketAddress &address, const char *buf, int size) { nsapi_size_or_error_t sent = NSAPI_ERROR_DEVICE_ERROR; int id; char *dataStr = (char *) malloc((size * 2) + 1); if (dataStr == NULL) { tr_error("Couldn't allocate memory for hex string conversion."); return NSAPI_ERROR_NO_MEMORY; } tr_debug("Converting byte array to hex string"); bin_to_hex(buf, size, dataStr); tr_debug("Got hex string"); // AT+NSOSTF= socket, remote_addr, remote_port, length, data tr_debug("Writing AT+NSOSTF=<sktid>,<ipaddr>,<port>,<flags>,<size>,<hex string> command..."); char *cmdStr = (char *) malloc(50); if (cmdStr == NULL) { tr_error("Couldn't allocate memory for AT cmd string."); return NSAPI_ERROR_NO_MEMORY; } int cmdsize = sprintf(cmdStr, "AT+NSOSTF=%d,\"%s\",%d,0x0,%d,\"", socket->modem_handle, address.get_ip_address(), address.get_port(), size); tr_debug("%s", cmdStr); LOCK(); if (_at->write(cmdStr, cmdsize) && sendATChopped(dataStr)) { tr_debug("Finished sending AT+NSOST comamnd, reading back the 'sent' size..."); if (_at->recv("%d,%d\n", &id, &sent) && _at->recv("OK")) { tr_debug("Sent %d bytes on socket %d", sent, id); } else { tr_error("Didn't get the Sent size or OK"); } } else { tr_error("Didn't send the AT command!"); } UNLOCK(); free(cmdStr); free(dataStr); return sent; } bool UbloxATCellularInterfaceN2xx::sendATChopped(const char *cmd) { char buff[SENDTO_CHUNK_SIZE]; tr_debug("Chopping up large AT text of %d characters.", strlen(cmd)); while (*cmd != '\0') { int i=0; for (i=0; i<SENDTO_CHUNK_SIZE; i++) { buff[i] = *cmd; // if we have copied the NULL terminator, we can exit if (*cmd == '\0') break; // still more characters to copy, so move along cmd++; } // if we are at the end of the line, use the send command to // provide the \r\n terminator for the AT command if (*cmd == '\0') { tr_debug("send(%d): %s", i, buff); // write the last buffer... if (!_at->write(buff,i)) return false; // ...send the enclosing quote to complete the AT command if (!_at->send("\"")) return false; } else { tr_debug("write(50): %s", buff); if (!_at->write(buff, 50)) return false; } } return true; } void UbloxATCellularInterfaceN2xx::bin_to_hex(const char *buff, unsigned int length, char *output) { char binHex[] = "0123456789ABCDEF"; *output = '\0'; for (; length > 0; --length) { unsigned char byte = *buff++; *output++ = binHex[(byte >> 4) & 0x0F]; *output++ = binHex[byte & 0x0F]; } *output++ = '\0'; } // Receive from a socket, TCP style. nsapi_size_or_error_t UbloxATCellularInterfaceN2xx::socket_recv(nsapi_socket_t handle, void *data, nsapi_size_t size) { return NSAPI_ERROR_UNSUPPORTED; } // Receive a packet over a UDP socket. nsapi_size_or_error_t UbloxATCellularInterfaceN2xx::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; int at_timeout = _at_timeout; char * tmpBuf = NULL; Timer timer; SockCtrl *socket = (SockCtrl *) handle; tr_debug("socket_recvfrom(0x%08x, 0x%08x, SIZE=%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_N2XX; 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); tmpBuf = (char *) malloc(read_blk); if (tmpBuf == NULL) { return NSAPI_ERROR_NO_MEMORY; } // call the AT helper function to get the bytes nsapi_error_size = receivefrom(socket->modem_handle, address, read_blk, tmpBuf); if (nsapi_error_size >= 0) { memcpy(buf, tmpBuf, nsapi_error_size); if (read_blk != (uint32_t) nsapi_error_size) tr_debug("Requested size is not the same as the returned size."); socket->pending -= nsapi_error_size; count += nsapi_error_size; buf += nsapi_error_size; size -= nsapi_error_size; if (((uint32_t) nsapi_error_size < read_blk) || (nsapi_error_size == MAX_READ_SIZE_N2XX)) 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 { // Should never fail to read when there is pending data success = false; } free(tmpBuf); } else if (timer.read_ms() < SOCKET_TIMEOUT) { // Wait for URCs tr_debug("Waiting for URC..."); _at->recv(UNNATURAL_STRING); } else { tr_debug("Nothing pending..."); if (count == 0) { tr_debug("Nothing received, so timeout with block"); // 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) { tr_debug("socket_recvfrom: %d SUCCESS!", count); nsapi_error_size = count; } else { tr_debug("socket_recvfrom: FAILED"); } return nsapi_error_size; } nsapi_size_or_error_t UbloxATCellularInterfaceN2xx::receivefrom(int modem_handle, SocketAddress *address, int length, char *buf) { char ipAddress[NSAPI_IP_SIZE]; nsapi_size_or_error_t size; memset (ipAddress, 0, sizeof (ipAddress)); // Ensure terminator if (length > MAX_READ_SIZE_N2XX) { return NSAPI_ERROR_UNSUPPORTED; } char * tmpBuf = (char *) malloc(length*2); if (tmpBuf == NULL) return NSAPI_ERROR_NO_MEMORY; int remaining; _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 // Ask for x bytes from Socket tr_debug("Requesting to read back %d bytes from socket %d", length, modem_handle); if (_at->send("AT+NSORF=%d,%d", modem_handle, length)) { unsigned int id, port; // ReadFrom header, to get length - if no data then this will time out if (_at->recv("%d,\"%15[^\"]\",%d,%d,", &id, ipAddress, &port, &size)) { tr_debug("Socket RecvFrom: #%d: %d", id, size); address->set_ip_address(ipAddress); address->set_port(port); // read the beginning quote for this data _at->read(tmpBuf, 1); // now read hex data if (_at->read(tmpBuf, size*2) == size*2) { // convert to bytes hex_to_bin(tmpBuf, buf, size); // read the "remaining" value - remembing there is an enclosing quote at the beginning of this read if (!_at->recv("\",%d\n", &remaining)) { tr_error("Failed reading the 'remaining' value after the received data."); size = NSAPI_ERROR_DEVICE_ERROR; } } } // we should get the OK (even if there is no data to read) if (_at->recv("OK")) { tr_debug("Socket RecvFrom: Read %d bytes, %d bytes remaining.", size, remaining); } else { tr_error("Socket RecvFrom: Didn't receive OK from AT+NSORF command."); size = NSAPI_ERROR_DEVICE_ERROR; } } _at->debug_on(_debug_trace_on); free(tmpBuf); return size; } char UbloxATCellularInterfaceN2xx::hex_char(char c) { if ('0' <= c && c <= '9') return (unsigned char)(c - '0'); if ('A' <= c && c <= 'F') return (unsigned char)(c - 'A' + 10); if ('a' <= c && c <= 'f') return (unsigned char)(c - 'a' + 10); return 0xFF; } int UbloxATCellularInterfaceN2xx::hex_to_bin(const char* s, char * buff, int length) { int result; if (!s || !buff || length <= 0) return -1; for (result = 0; *s; ++result) { unsigned char msn = hex_char(*s++); if (msn == 0xFF) return -1; unsigned char lsn = hex_char(*s++); if (lsn == 0xFF) return -1; unsigned char bin = (msn << 4) + lsn; if (length-- <= 0) return -1; *buff++ = bin; } return result; } // Attach an event callback to a socket, required for asynchronous // data reception void UbloxATCellularInterfaceN2xx::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 UbloxATCellularInterfaceN2xx::socket_listen(nsapi_socket_t handle, int backlog) { return NSAPI_ERROR_UNSUPPORTED; } nsapi_error_t UbloxATCellularInterfaceN2xx::socket_accept(nsapi_socket_t server, nsapi_socket_t *handle, SocketAddress *address) { return NSAPI_ERROR_UNSUPPORTED; } // Unsupported option functions. nsapi_error_t UbloxATCellularInterfaceN2xx::setsockopt(nsapi_socket_t handle, int level, int optname, const void *optval, unsigned optlen) { return NSAPI_ERROR_UNSUPPORTED; } nsapi_error_t UbloxATCellularInterfaceN2xx::getsockopt(nsapi_socket_t handle, int level, int optname, void *optval, unsigned *optlen) { return NSAPI_ERROR_UNSUPPORTED; } /********************************************************************** * PUBLIC METHODS **********************************************************************/ // Constructor. UbloxATCellularInterfaceN2xx::UbloxATCellularInterfaceN2xx(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; _localListenPort = 10000; tr_debug("UbloxATCellularInterfaceN2xx Constructor"); // 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, &UbloxATCellularInterfaceN2xx::handle_event)); // URC handlers for sockets _at->oob("+NSONMI", callback(this, &UbloxATCellularInterfaceN2xx::NSONMI_URC)); } // Destructor. UbloxATCellularInterfaceN2xx::~UbloxATCellularInterfaceN2xx() { // Free _ip if it was ever allocated free(_ip); } // Set the authentication scheme. void UbloxATCellularInterfaceN2xx::set_authentication(nsapi_security_t auth) { _auth = auth; } // Set APN, user name and password. void UbloxATCellularInterfaceN2xx::set_credentials(const char *apn, const char *uname, const char *pwd) { _apn = apn; _uname = uname; _pwd = pwd; } // Set PIN. void UbloxATCellularInterfaceN2xx::set_sim_pin(const char *pin) { set_pin(pin); } // Get the IP address of a host. nsapi_error_t UbloxATCellularInterfaceN2xx::gethostbyname(const char *host, SocketAddress *address, nsapi_version_t version) { nsapi_error_t nsapiError = NSAPI_ERROR_DEVICE_ERROR; tr_debug("GetHostByName: host= %s", host); if (address->set_ip_address(host)) { tr_debug("OK"); nsapiError = NSAPI_ERROR_OK; } else { tr_debug("Failed"); nsapiError = NSAPI_ERROR_UNSUPPORTED; } return nsapiError; } // Make a cellular connection nsapi_error_t UbloxATCellularInterfaceN2xx::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; } tr_debug("SIM, APN, UName & pwd set, now calling connect()"); nsapi_error = connect(); return nsapi_error; } bool UbloxATCellularInterfaceN2xx::initialise() { return init(); } // Make a cellular connection using the IP stack on board the cellular modem nsapi_error_t UbloxATCellularInterfaceN2xx::connect() { nsapi_error_t nsapi_error = NSAPI_ERROR_DEVICE_ERROR; bool registered = false; // Set up modem and then register with the network if (initialise()) { tr_debug("Trying to register..."); 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) { nsapi_error = NSAPI_ERROR_OK; } else { tr_debug("Failed to register."); } return nsapi_error; } // User initiated disconnect. nsapi_error_t UbloxATCellularInterfaceN2xx::disconnect() { nsapi_error_t nsapi_error = NSAPI_ERROR_DEVICE_ERROR; if (nwk_deregistration()) { nsapi_error = NSAPI_ERROR_OK; if (_connection_status_cb) { _connection_status_cb(NSAPI_ERROR_CONNECTION_LOST); } } return nsapi_error; } // Enable or disable SIM PIN check lock. nsapi_error_t UbloxATCellularInterfaceN2xx::set_sim_pin_check(bool set, bool immediate, const char *sim_pin) { return NSAPI_ERROR_UNSUPPORTED; } // Change the PIN code for the SIM card. nsapi_error_t UbloxATCellularInterfaceN2xx::set_new_sim_pin(const char *new_pin, bool immediate, const char *old_pin) { return NSAPI_ERROR_UNSUPPORTED; } // Determine if the connection is up. bool UbloxATCellularInterfaceN2xx::is_connected() { return get_ip_address() != NULL; } // Get the IP address of the on-board modem IP stack. const char * UbloxATCellularInterfaceN2xx::get_ip_address() { SocketAddress address; int id; 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 // +CGPADDR - returns a list of IP Addresses per context, just pick the first one as SARA-N2xx only allows 1 context. if (!_at->send("AT+CGPADDR") || !_at->recv("+CGPADDR:%d,%15[^\n]\n", &id, _ip) || ! _at->recv("OK") || !address.set_ip_address(_ip) || !address) { free (_ip); _ip = NULL; } } UNLOCK(); return _ip; } // Get the local network mask. const char *UbloxATCellularInterfaceN2xx::get_netmask() { // Not implemented. return NULL; } // Get the local gateways. const char *UbloxATCellularInterfaceN2xx::get_gateway() { return get_ip_address(); } void UbloxATCellularInterfaceN2xx::set_LocalListenPort(int port) { _localListenPort = port; } void UbloxATCellularInterfaceN2xx::set_plmn(const char *plmn) { } // Callback in case the connection is lost. void UbloxATCellularInterfaceN2xx::connection_status_cb(Callback<void(nsapi_error_t)> cb) { _connection_status_cb = cb; } // End of file