diff --git a/Utils/Multi_TCP_Echo.py b/Utils/Multi_TCP_Echo.py new file mode 100644 index 0000000..6288ae1 --- /dev/null +++ b/Utils/Multi_TCP_Echo.py @@ -0,0 +1,74 @@ +# Multiple TCP Echo Server +# Based on the third example from: +# https://rosettacode.org/wiki/Echo_server#Python + +#!usr/bin/env python +import socket +import threading + +NUM_PORTS = 5 # Echo on this many ports starting at PORT_BASE +PORT_BASE = 1200 # Change this if required +HOST = '192.168.0.50' # Change this to match your local IP +SOCKET_TIMEOUT = 30 + +# This function handles reading data sent by a client, echoing it back +# and closing the connection in case of timeout (30s) or "quit" command +# This function is meant to be started in a separate thread +# (one thread per client) +def handle_echo(client_connection, client_address, port): + client_connection.settimeout(SOCKET_TIMEOUT) + try: + while True: + data = client_connection.recv(1024) + # Close connection if "quit" received from client + if data == b'quit\r\n' or data == b'quit\n': + print('{} : {} disconnected'.format(client_address,port)) + client_connection.shutdown(1) + client_connection.close() + break + # Echo back to client + elif data: + print('FROM {} : {} : {}'.format(client_address,port,data)) + client_connection.send(data) + # Timeout and close connection after 30s of inactivity + except socket.timeout: + print('{} : {} timed out'.format(client_address,port)) + client_connection.shutdown(1) + client_connection.close() + +# This function opens a socket and listens on specified port. As soon as a +# connection is received, it is transfered to another socket so that the main +# socket is not blocked and can accept new clients. +def listen(host, port): + # Create the main socket (IPv4, TCP) + connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + connection.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + connection.bind((host, port)) + # Listen for clients (max 10 clients in waiting) + connection.listen(10) + # Every time a client connects, allow a dedicated socket and a dedicated + # thread to handle communication with that client without blocking others. + # Once the new thread has taken over, wait for the next client. + while True: + current_connection, client_address = connection.accept() + print('{} : {} connected'.format(client_address, port)) + handler_thread = threading.Thread( \ + target = handle_echo, \ + args = (current_connection,client_address,port) \ + ) + # daemon makes sure all threads are killed if the main server process + # gets killed + handler_thread.daemon = True + handler_thread.start() + +if __name__ == "__main__": + print('starting') + + threads = list() + + for i in range(NUM_PORTS): + threads.append( threading.Thread( \ + target = listen, args = (HOST, PORT_BASE + i)) ) + threads[i].daemon = True + threads[i].start() + diff --git a/Utils/Multi_UDP_Echo.py b/Utils/Multi_UDP_Echo.py new file mode 100644 index 0000000..8c3914b --- /dev/null +++ b/Utils/Multi_UDP_Echo.py @@ -0,0 +1,40 @@ +# Multiple UDP Echo Server +# Based on code by David Manouchehri +# Threading added by PaulZC + +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# Original author: David Manouchehri +# This script will always echo back data on the UDP port of your choice. +# Useful if you want nmap to report a UDP port as "open" instead of "open|filtered" on a standard scan. +# Works with both Python 2 & 3. + +import socket +import threading + +num_ports = 5 # Echo on this many ports starting at server_port_base +server_port_base = 1200 # Change this if required +server_address = '192.168.0.50' # Change this to match your local IP + +def echo(address, port): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + serv = (address, port) + sock.bind(serv) + while True: + payload, client_address = sock.recvfrom(4) # Try to receive 4 bytes + print("Port " + str(port) + ": Echoing 4 bytes back to " + str(client_address)) + sent = sock.sendto(payload, client_address) + +if __name__ == "__main__": + print("Listening on " + server_address) + + threads = list() + + for i in range(num_ports): + threads.append( threading.Thread( \ + target = echo, args = (server_address, server_port_base + i)) ) + threads[i].daemon = True + threads[i].start() + + diff --git a/examples/SARA-R5_Example10_SocketPingPong/SARA-R5_Example10_SocketPingPong.ino b/examples/SARA-R5_Example10_SocketPingPong/SARA-R5_Example10_SocketPingPong.ino index 5eb05b4..ec4ffd7 100644 --- a/examples/SARA-R5_Example10_SocketPingPong/SARA-R5_Example10_SocketPingPong.ino +++ b/examples/SARA-R5_Example10_SocketPingPong/SARA-R5_Example10_SocketPingPong.ino @@ -10,7 +10,8 @@ This example demonstrates how to transfer data from one SARA-R5 to another using TCP sockets. - This example includes the code from Example7_ConfigurePacketSwitchedData to let you see the SARA-R5's IP address. + The PDP profile is read from NVM. Please make sure you have run examples 4 & 7 previously to set up the profile. + If you select "Ping": The code asks for the IP Address of the "Pong" SARA-R5 The code then opens a TCP socket to the "Pong" SARA-R5 using port number TCP_PORT @@ -30,8 +31,7 @@ If that is the case, you can use this code to play ping-pong with another computer acting as a TCP Echo Server. Here's a quick how-to (assuming you are familiar with Python): Open up a Python editor on your computer - Grab yourself some simple TCP Echo Server code: - The third example here works well: https://rosettacode.org/wiki/Echo_server#Python + Copy the Multi_TCP_Echo.py from the GitHub repo Utils folder: https://github.com/sparkfun/SparkFun_u-blox_SARA-R5_Arduino_Library/tree/main/Utils Log in to your router Find your local IP address (usually 192.168.0.something) Go into your router's Security / Port Forwarding settings: @@ -39,14 +39,12 @@ The IP address is your local IP address Set the local port range to 1200-1200 (if you changed TCP_PORT, use that port number instead) Set the external port range to 1200-1200 - Set the protocol to TCP + Set the protocol to TCP (or BOTH) Enable the rule This will open up a direct connection from the outside world, through your router, to port 1200 on your computer Remember to lock it down again when you're done! - Edit the Python code and replace 'localhost' with your local IP number: + Edit the Python code and change 'HOST' to your local IP number: HOST = '192.168.0.nnn' - Change the PORT to 1200: - PORT = 1200 Run the Python code Ask Google for your computer's public IP address: Google "what is my IP address" @@ -85,6 +83,12 @@ SARA_R5 mySARA; // Change the pin number if required. //SARA_R5 mySARA(34); +// Create a SARA_R5 object to use throughout the sketch +// If you are using the LTE GNSS Breakout, and have access to the SARA's RESET_N pin, you can pass that to the library too +// allowing it to do an emergency shutdown if required. +// Change the pin numbers if required. +//SARA_R5 mySARA(34, 35); // PWR_ON, RESET_N + // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- unsigned int TCP_PORT = 1200; // Change this if required @@ -172,6 +176,8 @@ void processSocketData(int socket, String theData) // processSocketClose is provided to the SARA-R5 library via a // callback setter -- setSocketCloseCallback. (See setup()) +// +// Note: the SARA-R5 only sends a +UUSOCL URC when the socket os closed by the remote void processSocketClose(int socket) { Serial.println(); @@ -212,7 +218,7 @@ void setup() // Wait for user to press key to begin Serial.println(F("SARA-R5 Example")); - Serial.println(F("Press any key to begin")); + Serial.println(F("Wait until the SARA's NI LED lights up - then press any key to begin")); while (!Serial.available()) // Wait for the user to press a key (send any serial character) ; @@ -226,7 +232,7 @@ void setup() mySARA.invertPowerPin(true); // Initialize the SARA - if (mySARA.begin(saraSerial, 9600) ) + if (mySARA.begin(saraSerial, 115200) ) { Serial.println(F("SARA-R5 connected!")); } @@ -253,86 +259,22 @@ void setup() ; // Do nothing more } - int minCID = SARA_R5_NUM_PDP_CONTEXT_IDENTIFIERS; // Keep a record of the highest and lowest CIDs - int maxCID = 0; - - Serial.println(F("The available Context IDs are:")); - Serial.println(F("Context ID:\tAPN Name:\tIP Address:")); - for (int cid = 0; cid < SARA_R5_NUM_PDP_CONTEXT_IDENTIFIERS; cid++) - { - String apn = ""; - IPAddress ip(0, 0, 0, 0); - mySARA.getAPN(cid, &apn, &ip); - if (apn.length() > 0) - { - Serial.print(cid); - Serial.print(F("\t")); - Serial.print(apn); - Serial.print(F("\t")); - Serial.println(ip); - } - if (cid < minCID) - minCID = cid; // Record the lowest CID - if (cid > maxCID) - maxCID = cid; // Record the highest CID - } - Serial.println(); - - Serial.println(F("Which Context ID do you want to use for your Packet Switched Data connection?")); - Serial.println(F("Please enter the number (followed by LF / Newline): ")); - - char c = 0; - bool selected = false; - int selection = 0; - while (!selected) - { - while (!Serial.available()) ; // Wait for a character to arrive - c = Serial.read(); // Read it - if (c == '\n') // Is it a LF? - { - if ((selection >= minCID) && (selection <= maxCID)) - { - selected = true; - Serial.println("Using CID: " + String(selection)); - } - else - { - Serial.println(F("Invalid CID. Please try again:")); - selection = 0; - } - } - else - { - selection *= 10; // Multiply selection by 10 - selection += c - '0'; // Add the new digit to selection - } - } - - // Deactivate the profile + // Deactivate the profile - in case one is already active if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_DEACTIVATE) != SARA_R5_SUCCESS) { Serial.println(F("Warning: performPDPaction (deactivate profile) failed. Probably because no profile was active.")); } - // Map PSD profile 0 to the selected CID - if (mySARA.setPDPconfiguration(0, SARA_R5_PSD_CONFIG_PARAM_MAP_TO_CID, selection) != SARA_R5_SUCCESS) - { - Serial.println(F("setPDPconfiguration (map to CID) failed! Freezing...")); - while (1) - ; // Do nothing more - } - - // Set the protocol type - this needs to match the defined IP type for the CID (as opposed to what was granted by the network) - if (mySARA.setPDPconfiguration(0, SARA_R5_PSD_CONFIG_PARAM_PROTOCOL, SARA_R5_PSD_PROTOCOL_IPV4V6_V4_PREF) != SARA_R5_SUCCESS) - // You _may_ need to change the protocol type: ----------------------------------------^ + // Load the profile from NVM - these were saved by a previous example + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_LOAD) != SARA_R5_SUCCESS) { - Serial.println(F("setPDPconfiguration (set protocol type) failed! Freezing...")); + Serial.println(F("performPDPaction (load from NVM) failed! Freezing...")); while (1) ; // Do nothing more } - // Set a callback to process the results of the PSD Action - mySARA.setPSDActionCallback(&processPSDAction); + // Set a callback to process the results of the PSD Action - OPTIONAL + //mySARA.setPSDActionCallback(&processPSDAction); // Activate the profile if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_ACTIVATE) != SARA_R5_SUCCESS) @@ -342,19 +284,17 @@ void setup() ; // Do nothing more } - for (int i = 0; i < 100; i++) // Wait for up to a second for the PSD Action URC to arrive - { - mySARA.bufferedPoll(); // Keep processing data from the SARA so we can process the PSD Action - delay(10); - } + //for (int i = 0; i < 100; i++) // Wait for up to a second for the PSD Action URC to arrive - OPTIONAL + //{ + // mySARA.bufferedPoll(); // Keep processing data from the SARA so we can process the PSD Action + // delay(10); + //} - // Save the profile to NVM - so we can load it again in the later examples - if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_STORE) != SARA_R5_SUCCESS) - { - Serial.println(F("performPDPaction (save to NVM) failed! Freezing...")); - while (1) - ; // Do nothing more - } + //Print the dynamic IP Address (for profile 0) + IPAddress myAddress; + mySARA.getNetworkAssignedIPAddress(0, &myAddress); + Serial.print(F("\r\nMy IP Address is: ")); + Serial.println(myAddress.toString()); // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- @@ -369,6 +309,8 @@ void setup() // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- // Set a callback to process the socket close + // + // Note: the SARA-R5 only sends a +UUSOCL URC when the socket os closed by the remote mySARA.setSocketCloseCallback(&processSocketClose); // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- @@ -378,9 +320,9 @@ void setup() Serial.println(F("2: Pong")); Serial.println(F("\r\nPlease enter the number (followed by LF / Newline): ")); - c = 0; - selected = false; - selection = 0; + char c = 0; + bool selected = false; + int selection = 0; while (!selected) { while (!Serial.available()) ; // Wait for a character to arrive @@ -401,7 +343,7 @@ void setup() selection = 0; } } - else + else if ((c >= '0') && (c <= '9')) { selection = c - '0'; // Store a single digit } @@ -443,17 +385,15 @@ void setup() field++; // Increment the field val = 0; // Reset the value } - else + else if ((c >= '0') && (c <= '9')) { val *= 10; // Multiply by 10 val += c - '0'; // Add the digit } } - char theAddressString[16]; - sprintf(theAddressString, "%d.%d.%d.%d", theAddress[0], theAddress[1], theAddress[2], theAddress[3]); Serial.print(F("Remote address is ")); - Serial.println(theAddressString); + Serial.println(theAddress.toString()); // Open the socket socketNum = mySARA.socketOpen(SARA_R5_TCP); @@ -468,7 +408,7 @@ void setup() Serial.println(socketNum); // Connect to the remote IP Address - if (mySARA.socketConnect(socketNum, (const char *)theAddressString, TCP_PORT) != SARA_R5_SUCCESS) + if (mySARA.socketConnect(socketNum, theAddress, TCP_PORT) != SARA_R5_SUCCESS) { Serial.println(F("socketConnect failed! Freezing...")); while (1) @@ -519,6 +459,8 @@ void setup() } +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + void loop() { mySARA.bufferedPoll(); // Process the backlog (if any) and any fresh serial data @@ -527,6 +469,7 @@ void loop() { if (pingCount >= pingPongLimit) { + printSocketParameters(socketNum); mySARA.socketClose(socketNum); // Close the socket - no more pings will be sent while (1) mySARA.bufferedPoll(); // Do nothing more except process any received data @@ -537,9 +480,83 @@ void loop() { if (millis() > (startTime + timeLimit)) { + printSocketParameters(socketNum); mySARA.socketClose(socketNum); // Close the socket - no more pongs will be sent while (1) mySARA.bufferedPoll(); // Do nothing more except process any received data } } } + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// Print the socket parameters +// Note: the socket must be open. ERRORs will be returned if the socket is closed. +void printSocketParameters(int socket) +{ + Serial.println(F("Socket parameters:")); + + Serial.print(F("Socket type: ")); + SARA_R5_socket_protocol_t socketType; + mySARA.querySocketType(socket, &socketType); + if (socketType == SARA_R5_TCP) + Serial.println(F("TCP")); + else if (socketType == SARA_R5_UDP) + Serial.println(F("UDP")); + else + Serial.println(F("UNKNOWN! (Error!)")); + + Serial.print(F("Last error: ")); + int lastError; + mySARA.querySocketLastError(socket, &lastError); + Serial.println(lastError); + + Serial.print(F("Total bytes sent: ")); + uint32_t bytesSent; + mySARA.querySocketTotalBytesSent(socket, &bytesSent); + Serial.println(bytesSent); + + Serial.print(F("Total bytes received: ")); + uint32_t bytesReceived; + mySARA.querySocketTotalBytesReceived(socket, &bytesReceived); + Serial.println(bytesReceived); + + Serial.print(F("Remote IP Address: ")); + IPAddress remoteAddress; + int remotePort; + mySARA.querySocketRemoteIPAddress(socket, &remoteAddress, &remotePort); + Serial.println(remoteAddress.toString()); + + Serial.print(F("Socket status (TCP sockets only): ")); + SARA_R5_tcp_socket_status_t socketStatus; + mySARA.querySocketStatusTCP(socket, &socketStatus); + if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_INACTIVE) + Serial.println(F("INACTIVE")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_LISTEN) + Serial.println(F("LISTEN")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_SYN_SENT) + Serial.println(F("SYN_SENT")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_SYN_RCVD) + Serial.println(F("SYN_RCVD")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_ESTABLISHED) + Serial.println(F("ESTABLISHED")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_1) + Serial.println(F("FIN_WAIT_1")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_2) + Serial.println(F("FIN_WAIT_2")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_CLOSE_WAIT) + Serial.println(F("CLOSE_WAIT")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_CLOSING) + Serial.println(F("CLOSING")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_LAST_ACK) + Serial.println(F("LAST_ACK")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_TIME_WAIT) + Serial.println(F("TIME_WAIT")); + else + Serial.println(F("UNKNOWN! (Error!)")); + + Serial.print(F("Unacknowledged outgoing bytes: ")); + uint32_t bytesUnack; + mySARA.querySocketOutUnackData(socket, &bytesUnack); + Serial.println(bytesUnack); +} diff --git a/examples/SARA-R5_Example10_SocketPingPong_BinaryTCP/SARA-R5_Example10_SocketPingPong_BinaryTCP.ino b/examples/SARA-R5_Example10_SocketPingPong_BinaryTCP/SARA-R5_Example10_SocketPingPong_BinaryTCP.ino new file mode 100644 index 0000000..9f15b25 --- /dev/null +++ b/examples/SARA-R5_Example10_SocketPingPong_BinaryTCP/SARA-R5_Example10_SocketPingPong_BinaryTCP.ino @@ -0,0 +1,578 @@ +/* + + SARA-R5 Example + =============== + + Socket "Ping Pong" - Binary TCP Data Transfers + + Written by: Paul Clark + Date: December 30th 2021 + + This example demonstrates how to transfer binary data from one SARA-R5 to another using TCP sockets. + + The PDP profile is read from NVM. Please make sure you have run examples 4 & 7 previously to set up the profile. + + If you select "Ping": + The code asks for the IP Address of the "Pong" SARA-R5 + The code then opens a TCP socket to the "Pong" SARA-R5 using port number TCP_PORT + The code sends an initial "Ping" (Binary 0x00, 0x01, 0x02, 0x03) using Write Socket Data (+USOWR) + The code polls continuously. When a +UUSORD URC message is received, data is read and passed to the socketReadCallback. + When "Pong" (Binary 0x04, 0x05, 0x06, 0x07) is received by the callback, the code sends "Ping" in reply + The Ping-Pong repeats 100 times + The socket is closed after the 100th Ping is sent + If you select "Pong": + The code opens a TCP socket and waits for a connection and for data to arrive + The code polls continuously. When a +UUSORD URC message is received, data is read and passed to the socketReadCallback. + When "Ping" is received by the callback, the code sends "Pong" in reply + The socket is closed after 120 seconds + Start the "Pong" first! + + You may find that your service provider is blocking incoming TCP connections to the SARA-R5, preventing the "Pong" from working... + If that is the case, you can use this code to play ping-pong with another computer acting as a TCP Echo Server. + Here's a quick how-to (assuming you are familiar with Python): + Open up a Python editor on your computer + Copy the Multi_TCP_Echo.py from the GitHub repo Utils folder: https://github.com/sparkfun/SparkFun_u-blox_SARA-R5_Arduino_Library/tree/main/Utils + Log in to your router + Find your local IP address (usually 192.168.0.something) + Go into your router's Security / Port Forwarding settings: + Create a new port forwarding rule + The IP address is your local IP address + Set the local port range to 1200-1200 (if you changed TCP_PORT, use that port number instead) + Set the external port range to 1200-1200 + Set the protocol to TCP (or BOTH) + Enable the rule + This will open up a direct connection from the outside world, through your router, to port 1200 on your computer + Remember to lock it down again when you're done! + Edit the Python code and change 'HOST' to your local IP number: + HOST = '192.168.0.nnn' + Run the Python code + Ask Google for your computer's public IP address: + Google "what is my IP address" + Run this code and choose the "Ping" option + Enter your computer's public IP address when asked + Sit back and watch the ping-pong! + The code will stop after 100 Pings+Echos and 100 Pongs+Echos + That's 400 TCP transfers in total! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + + Licence: MIT + Please see LICENSE.md for full details + +*/ + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_SARA-R5_Arduino_Library + +// Uncomment the next line to connect to the SARA-R5 using hardware Serial1 +#define saraSerial Serial1 + +// Uncomment the next line to create a SoftwareSerial object to pass to the SARA-R5 library instead +//SoftwareSerial saraSerial(8, 9); + +// Create a SARA_R5 object to use throughout the sketch +// Usually we would tell the library which GPIO pin to use to control the SARA power (see below), +// but we can start the SARA without a power pin. It just means we need to manually +// turn the power on if required! ;-D +SARA_R5 mySARA; + +// Create a SARA_R5 object to use throughout the sketch +// We need to tell the library what GPIO pin is connected to the SARA power pin. +// If you're using the MicroMod Asset Tracker and the MicroMod Artemis Processor Board, +// the pin name is G2 which is connected to pin AD34. +// Change the pin number if required. +//SARA_R5 mySARA(34); + +// Create a SARA_R5 object to use throughout the sketch +// If you are using the LTE GNSS Breakout, and have access to the SARA's RESET_N pin, you can pass that to the library too +// allowing it to do an emergency shutdown if required. +// Change the pin numbers if required. +//SARA_R5 mySARA(34, 35); // PWR_ON, RESET_N + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +unsigned int TCP_PORT = 1200; // Change this if required + +bool iAmPing; + +// Keep track of how many ping-pong exchanges have taken place. "Ping" closes the socket when pingCount reaches pingPongLimit. +volatile int pingCount = 0; +volatile int pongCount = 0; +const int pingPongLimit = 100; + +// Keep track of how long the socket has been open. "Pong" closes the socket when timeLimit (millis) is reached. +unsigned long startTime; +const unsigned long timeLimit = 120000; // 120 seconds + +#include // Needed for sockets +volatile int socketNum; + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processSocketListen is provided to the SARA-R5 library via a +// callback setter -- setSocketListenCallback. (See setup()) +void processSocketListen(int listeningSocket, IPAddress localIP, unsigned int listeningPort, int socket, IPAddress remoteIP, unsigned int port) +{ + Serial.println(); + Serial.print(F("Socket connection made: listeningSocket ")); + Serial.print(listeningSocket); + Serial.print(F(" localIP ")); + Serial.print(localIP[0]); + Serial.print(F(".")); + Serial.print(localIP[1]); + Serial.print(F(".")); + Serial.print(localIP[2]); + Serial.print(F(".")); + Serial.print(localIP[3]); + Serial.print(F(" listeningPort ")); + Serial.print(listeningPort); + Serial.print(F(" socket ")); + Serial.print(socket); + Serial.print(F(" remoteIP ")); + Serial.print(remoteIP[0]); + Serial.print(F(".")); + Serial.print(remoteIP[1]); + Serial.print(F(".")); + Serial.print(remoteIP[2]); + Serial.print(F(".")); + Serial.print(remoteIP[3]); + Serial.print(F(" port ")); + Serial.println(port); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processSocketData is provided to the SARA-R5 library via a +// callback setter -- setSocketReadCallback. (See setup()) +void processSocketData(int socket, String theData) +{ + Serial.println(); + Serial.print(F("Data received on socket ")); + Serial.print(socket); + Serial.print(F(" :")); + for (int i = 0; i < theData.length(); i++) + { + Serial.print(F(" 0x")); + if (theData[i] < 16) + Serial.print(F("0")); + Serial.print(theData[i], HEX); + } + Serial.println(); + + if ((theData[0] == 0x00) && (theData[1] == 0x01) && (theData[2] == 0x02) && (theData[3] == 0x03)) // Look for the "Ping" + { + const char pong[] = { 0x04, 0x05, 0x06, 0x07 }; + mySARA.socketWrite(socket, pong, 4); // Send the "Pong" + pongCount++; + } + + if ((theData[0] == 0x04) && (theData[1] == 0x05) && (theData[2] == 0x06) && (theData[3] == 0x07)) // Look for the "Pong" + { + // Use the const char * version + //const char ping[] = { 0x00, 0x01, 0x02, 0x03 }; + //mySARA.socketWrite(socket, ping, 4); // Send the "Ping" + + // Or use the String version. Both are OK for binary data. + String ping = ""; + ping.concat('\0'); // Construct the ping in a binary-friendly way + ping.concat('\1'); + ping.concat('\2'); + ping.concat('\3'); + mySARA.socketWrite(socket, ping); // Send the "Ping" + + pingCount++; + } + + Serial.print(F("pingCount = ")); + Serial.print(pingCount); + Serial.print(F(" : pongCount = ")); + Serial.println(pongCount); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processSocketClose is provided to the SARA-R5 library via a +// callback setter -- setSocketCloseCallback. (See setup()) +// +// Note: the SARA-R5 only sends a +UUSOCL URC when the socket os closed by the remote +void processSocketClose(int socket) +{ + Serial.println(); + Serial.print(F("Socket ")); + Serial.print(socket); + Serial.println(F(" closed!")); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processPSDAction is provided to the SARA-R5 library via a +// callback setter -- setPSDActionCallback. (See setup()) +void processPSDAction(int result, IPAddress ip) +{ + Serial.println(); + Serial.print(F("PSD Action: result: ")); + Serial.print(String(result)); + if (result == 0) + Serial.print(F(" (success)")); + Serial.print(F(" IP Address: \"")); + Serial.print(String(ip[0])); + Serial.print(F(".")); + Serial.print(String(ip[1])); + Serial.print(F(".")); + Serial.print(String(ip[2])); + Serial.print(F(".")); + Serial.print(String(ip[3])); + Serial.println(F("\"")); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void setup() +{ + String currentOperator = ""; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("SARA-R5 Example")); + Serial.println(F("Wait until the SARA's NI LED lights up - then press any key to begin")); + + while (!Serial.available()) // Wait for the user to press a key (send any serial character) + ; + while (Serial.available()) // Empty the serial RX buffer + Serial.read(); + + //mySARA.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + // For the MicroMod Asset Tracker, we need to invert the power pin so it pulls high instead of low + // Comment the next line if required + mySARA.invertPowerPin(true); + + // Initialize the SARA + if (mySARA.begin(saraSerial, 115200) ) + { + Serial.println(F("SARA-R5 connected!")); + } + else + { + Serial.println(F("Unable to communicate with the SARA.")); + Serial.println(F("Manually power-on (hold the SARA On button for 3 seconds) on and try again.")); + while (1) ; // Loop forever on fail + } + Serial.println(); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // First check to see if we're connected to an operator: + if (mySARA.getOperator(¤tOperator) == SARA_R5_SUCCESS) + { + Serial.print(F("Connected to: ")); + Serial.println(currentOperator); + } + else + { + Serial.print(F("The SARA is not yet connected to an operator. Please use the previous examples to connect. Or wait and retry. Freezing...")); + while (1) + ; // Do nothing more + } + + // Deactivate the profile - in case one is already active + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_DEACTIVATE) != SARA_R5_SUCCESS) + { + Serial.println(F("Warning: performPDPaction (deactivate profile) failed. Probably because no profile was active.")); + } + + // Load the profile from NVM - these were saved by a previous example + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_LOAD) != SARA_R5_SUCCESS) + { + Serial.println(F("performPDPaction (load from NVM) failed! Freezing...")); + while (1) + ; // Do nothing more + } + + // Set a callback to process the results of the PSD Action - OPTIONAL + //mySARA.setPSDActionCallback(&processPSDAction); + + // Activate the profile + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_ACTIVATE) != SARA_R5_SUCCESS) + { + Serial.println(F("performPDPaction (activate profile) failed! Freezing...")); + while (1) + ; // Do nothing more + } + + //for (int i = 0; i < 100; i++) // Wait for up to a second for the PSD Action URC to arrive - OPTIONAL + //{ + // mySARA.bufferedPoll(); // Keep processing data from the SARA so we can process the PSD Action + // delay(10); + //} + + //Print the dynamic IP Address (for profile 0) + IPAddress myAddress; + mySARA.getNetworkAssignedIPAddress(0, &myAddress); + Serial.print(F("\r\nMy IP Address is: ")); + Serial.println(myAddress.toString()); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // Set a callback to process the socket listen + mySARA.setSocketListenCallback(&processSocketListen); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // Set a callback to process the socket data + mySARA.setSocketReadCallback(&processSocketData); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // Set a callback to process the socket close + // + // Note: the SARA-R5 only sends a +UUSOCL URC when the socket os closed by the remote + mySARA.setSocketCloseCallback(&processSocketClose); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + Serial.println(F("\r\nDo you want to Ping or Pong? (Always start the Pong first!)\r\n")); + Serial.println(F("1: Ping")); + Serial.println(F("2: Pong")); + Serial.println(F("\r\nPlease enter the number (followed by LF / Newline): ")); + + char c = 0; + bool selected = false; + int selection = 0; + while (!selected) + { + while (!Serial.available()) ; // Wait for a character to arrive + c = Serial.read(); // Read it + if (c == '\n') // Is it a LF? + { + if ((selection >= 1) && (selection <= 2)) + { + selected = true; + if (selection == 1) + Serial.println(F("\r\nPing selected!")); + else + Serial.println(F("\r\nPong selected!")); + } + else + { + Serial.println(F("Invalid choice. Please try again:")); + selection = 0; + } + } + else if ((c >= '0') && (c <= '9')) + { + selection = c - '0'; // Store a single digit + } + } + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (selection == 1) // Ping + { + iAmPing = true; + + Serial.println(F("\r\nPlease enter the IP number you want to ping (nnn.nnn.nnn.nnn followed by LF / Newline): ")); + + char c = 0; + bool selected = false; + int val = 0; + IPAddress theAddress = {0,0,0,0}; + int field = 0; + while (!selected) + { + while (!Serial.available()) ; // Wait for a character to arrive + c = Serial.read(); // Read it + if (c == '\n') // Is it a LF? + { + theAddress[field] = val; // Store the current value + if (field == 3) + selected = true; + else + { + Serial.println(F("Invalid IP Address. Please try again:")); + val = 0; + field = 0; + } + } + else if (c == '.') // Is it a separator + { + theAddress[field] = val; // Store the current value + if (field <= 2) + field++; // Increment the field + val = 0; // Reset the value + } + else if ((c >= '0') && (c <= '9')) + { + val *= 10; // Multiply by 10 + val += c - '0'; // Add the digit + } + } + + Serial.print(F("Remote address is ")); + Serial.println(theAddress.toString()); + + // Open the socket + socketNum = mySARA.socketOpen(SARA_R5_TCP); + if (socketNum == -1) + { + Serial.println(F("socketOpen failed! Freezing...")); + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + + Serial.print(F("Using socket ")); + Serial.println(socketNum); + + // Connect to the remote IP Address + if (mySARA.socketConnect(socketNum, theAddress, TCP_PORT) != SARA_R5_SUCCESS) + { + Serial.println(F("socketConnect failed! Freezing...")); + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + else + { + Serial.println(F("Socket connected!")); + } + + // Send the first ping to start the ping-pong + const char ping[] = { 0x00, 0x01, 0x02, 0x03 }; + mySARA.socketWrite(socketNum, ping, 4); // Send the "Ping" + } + + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + else // if (selection == 2) // Pong + { + iAmPing = false; + + // Open the socket + socketNum = mySARA.socketOpen(SARA_R5_TCP); + if (socketNum == -1) + { + Serial.println(F("socketOpen failed! Freezing...")); + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + + Serial.print(F("Using socket ")); + Serial.println(socketNum); + + // Start listening for a connection + if (mySARA.socketListen(socketNum, TCP_PORT) != SARA_R5_SUCCESS) + { + Serial.println(F("socketListen failed! Freezing...")); + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + } + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + startTime = millis(); // Record that start time + +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void loop() +{ + mySARA.bufferedPoll(); // Process the backlog (if any) and any fresh serial data + + if (iAmPing) // Ping - close the socket when we've reached pingPongLimit + { + if (pingCount >= pingPongLimit) + { + printSocketParameters(socketNum); + mySARA.socketClose(socketNum); // Close the socket - no more pings will be sent + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + } + + else // Pong - close the socket when we've reached the timeLimit + { + if (millis() > (startTime + timeLimit)) + { + printSocketParameters(socketNum); + mySARA.socketClose(socketNum); // Close the socket - no more pongs will be sent + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + } +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// Print the socket parameters +// Note: the socket must be open. ERRORs will be returned if the socket is closed. +void printSocketParameters(int socket) +{ + Serial.println(F("Socket parameters:")); + + Serial.print(F("Socket type: ")); + SARA_R5_socket_protocol_t socketType; + mySARA.querySocketType(socket, &socketType); + if (socketType == SARA_R5_TCP) + Serial.println(F("TCP")); + else if (socketType == SARA_R5_UDP) + Serial.println(F("UDP")); + else + Serial.println(F("UNKNOWN! (Error!)")); + + Serial.print(F("Last error: ")); + int lastError; + mySARA.querySocketLastError(socket, &lastError); + Serial.println(lastError); + + Serial.print(F("Total bytes sent: ")); + uint32_t bytesSent; + mySARA.querySocketTotalBytesSent(socket, &bytesSent); + Serial.println(bytesSent); + + Serial.print(F("Total bytes received: ")); + uint32_t bytesReceived; + mySARA.querySocketTotalBytesReceived(socket, &bytesReceived); + Serial.println(bytesReceived); + + Serial.print(F("Remote IP Address: ")); + IPAddress remoteAddress; + int remotePort; + mySARA.querySocketRemoteIPAddress(socket, &remoteAddress, &remotePort); + Serial.println(remoteAddress.toString()); + + Serial.print(F("Socket status (TCP sockets only): ")); + SARA_R5_tcp_socket_status_t socketStatus; + mySARA.querySocketStatusTCP(socket, &socketStatus); + if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_INACTIVE) + Serial.println(F("INACTIVE")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_LISTEN) + Serial.println(F("LISTEN")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_SYN_SENT) + Serial.println(F("SYN_SENT")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_SYN_RCVD) + Serial.println(F("SYN_RCVD")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_ESTABLISHED) + Serial.println(F("ESTABLISHED")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_1) + Serial.println(F("FIN_WAIT_1")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_2) + Serial.println(F("FIN_WAIT_2")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_CLOSE_WAIT) + Serial.println(F("CLOSE_WAIT")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_CLOSING) + Serial.println(F("CLOSING")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_LAST_ACK) + Serial.println(F("LAST_ACK")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_TIME_WAIT) + Serial.println(F("TIME_WAIT")); + else + Serial.println(F("UNKNOWN! (Error!)")); + + Serial.print(F("Unacknowledged outgoing bytes: ")); + uint32_t bytesUnack; + mySARA.querySocketOutUnackData(socket, &bytesUnack); + Serial.println(bytesUnack); +} diff --git a/examples/SARA-R5_Example10_SocketPingPong_MultipleTCP/SARA-R5_Example10_SocketPingPong_MultipleTCP.ino b/examples/SARA-R5_Example10_SocketPingPong_MultipleTCP/SARA-R5_Example10_SocketPingPong_MultipleTCP.ino new file mode 100644 index 0000000..db49fa0 --- /dev/null +++ b/examples/SARA-R5_Example10_SocketPingPong_MultipleTCP/SARA-R5_Example10_SocketPingPong_MultipleTCP.ino @@ -0,0 +1,606 @@ +/* + + SARA-R5 Example + =============== + + Socket "Ping Pong" - TCP Data Transfers on multiple sockets + + Written by: Paul Clark + Date: December 30th 2021 + + This example demonstrates how to transfer data from one SARA-R5 to another using multiple TCP sockets. + + The PDP profile is read from NVM. Please make sure you have run examples 4 & 7 previously to set up the profile. + + If you select "Ping": + The code asks for the IP Address of the "Pong" SARA-R5 + The code then opens multiple TCP sockets to the "Pong" SARA-R5 using port number TCP_PORT_BASE, TCP_PORT_BASE + 1, etc. + The code sends an initial "Ping" using Write Socket Data (+USOWR) + The code polls continuously. When a +UUSORD URC message is received, data is read and passed to the socketReadCallback. + When "Pong" is received by the callback, the code sends "Ping" in reply + The Ping-Pong repeats 20 times + The socket is closed after the 20th Ping is sent + If you select "Pong": + The code opens multiple TCP sockets and waits for a connection and for data to arrive + The code polls continuously. When a +UUSORD URC message is received, data is read and passed to the socketReadCallback. + When "Ping" is received by the callback, the code sends "Pong" in reply + The socket is closed after 120 seconds + Start the "Pong" first! + + You may find that your service provider is blocking incoming TCP connections to the SARA-R5, preventing the "Pong" from working... + If that is the case, you can use this code to play ping-pong with another computer acting as a TCP Echo Server. + Here's a quick how-to (assuming you are familiar with Python): + Open up a Python editor on your computer + Copy the Multi_TCP_Echo.py from the GitHub repo Utils folder: https://github.com/sparkfun/SparkFun_u-blox_SARA-R5_Arduino_Library/tree/main/Utils + Log in to your router + Find your computer's local IP address (usually 192.168.0.something) + Go into your router's Security / Port Forwarding settings: + Create a new port forwarding rule + The IP address is your local IP address + Set the local port range to 1200-1206 (if you changed TCP_PORT_BASE, use that port number instead) + Set the external port range to 1200-1206 + Set the protocol to TCP (or BOTH) + Enable the rule + This will open up a direct connection from the outside world, through your router, to ports 1200-1206 on your computer + Remember to lock them down again when you're done! + Edit the Python code and change 'HOST' to your local IP number: + HOST = '192.168.0.nnn' + Run the Python code + Ask Google for your computer's public IP address: + Google "what is my IP address" + Run this code and choose the "Ping" option + Enter your computer's public IP address when asked + Sit back and watch the ping-pong! + The code will stop after 20 Pings+Echos and 20 Pongs+Echos on each port + On 5 ports, that's 400 TCP transfers in total! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + + Licence: MIT + Please see LICENSE.md for full details + +*/ + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_SARA-R5_Arduino_Library + +// Uncomment the next line to connect to the SARA-R5 using hardware Serial1 +#define saraSerial Serial1 + +// Uncomment the next line to create a SoftwareSerial object to pass to the SARA-R5 library instead +//SoftwareSerial saraSerial(8, 9); + +// Create a SARA_R5 object to use throughout the sketch +// Usually we would tell the library which GPIO pin to use to control the SARA power (see below), +// but we can start the SARA without a power pin. It just means we need to manually +// turn the power on if required! ;-D +SARA_R5 mySARA; + +// Create a SARA_R5 object to use throughout the sketch +// We need to tell the library what GPIO pin is connected to the SARA power pin. +// If you're using the MicroMod Asset Tracker and the MicroMod Artemis Processor Board, +// the pin name is G2 which is connected to pin AD34. +// Change the pin number if required. +//SARA_R5 mySARA(34); + +// Create a SARA_R5 object to use throughout the sketch +// If you are using the LTE GNSS Breakout, and have access to the SARA's RESET_N pin, you can pass that to the library too +// allowing it to do an emergency shutdown if required. +// Change the pin numbers if required. +//SARA_R5 mySARA(34, 35); // PWR_ON, RESET_N + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +const int numConnections = 5; // How many sockets do you want to use? Max is 7. +unsigned int TCP_PORT_BASE = 1200; // Change this if required + +bool iAmPing; + +// Keep track of how many ping-pong exchanges have taken place. "Ping" closes the socket when pingCount reaches pingPongLimit. +volatile int pingCount[numConnections]; +volatile int pongCount[numConnections]; +const int pingPongLimit = 20; + +// Keep track of how long the socket has been open. "Pong" closes the socket when timeLimit (millis) is reached. +unsigned long startTime; +const unsigned long timeLimit = 120000; // 120 seconds + +#include // Needed for sockets +volatile int socketNum[numConnections]; // Record the socket numbers. -1 indicates the socket is invalid or closed. + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processSocketListen is provided to the SARA-R5 library via a +// callback setter -- setSocketListenCallback. (See setup()) +void processSocketListen(int listeningSocket, IPAddress localIP, unsigned int listeningPort, int socket, IPAddress remoteIP, unsigned int port) +{ + Serial.println(); + Serial.print(F("Socket connection made: listeningSocket ")); + Serial.print(listeningSocket); + Serial.print(F(" localIP ")); + Serial.print(localIP[0]); + Serial.print(F(".")); + Serial.print(localIP[1]); + Serial.print(F(".")); + Serial.print(localIP[2]); + Serial.print(F(".")); + Serial.print(localIP[3]); + Serial.print(F(" listeningPort ")); + Serial.print(listeningPort); + Serial.print(F(" socket ")); + Serial.print(socket); + Serial.print(F(" remoteIP ")); + Serial.print(remoteIP[0]); + Serial.print(F(".")); + Serial.print(remoteIP[1]); + Serial.print(F(".")); + Serial.print(remoteIP[2]); + Serial.print(F(".")); + Serial.print(remoteIP[3]); + Serial.print(F(" port ")); + Serial.println(port); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processSocketData is provided to the SARA-R5 library via a +// callback setter -- setSocketReadCallback. (See setup()) +void processSocketData(int socket, String theData) +{ + int connection = -1; + for (int i = 0; i < numConnections; i++) + if (socketNum[i] == socket) + connection = i; + + if (connection == -1) + { + Serial.println(); + Serial.print(F("Data received on unexpected socket ")); + Serial.println(socket); + return; + } + + Serial.println(); + Serial.print(F("Data received on socket ")); + Serial.print(socket); + Serial.print(F(" : ")); + Serial.println(theData); + + if (theData == String("Ping")) // Look for the "Ping" + { + if (pongCount[connection] < pingPongLimit) + { + const char pong[] = "Pong"; + mySARA.socketWrite(socket, pong); // Send the "Pong" + pongCount[connection]++; + } + } + + if (theData == String("Pong")) // Look for the "Pong" + { + if (pingCount[connection] < pingPongLimit) + { + const char ping[] = "Ping"; + mySARA.socketWrite(socket, ping); // Send the "Ping" + pingCount[connection]++; + } + } + + Serial.print(F("pingCount = ")); + Serial.print(pingCount[connection]); + Serial.print(F(" : pongCount = ")); + Serial.println(pongCount[connection]); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processSocketClose is provided to the SARA-R5 library via a +// callback setter -- setSocketCloseCallback. (See setup()) +// +// Note: the SARA-R5 only sends a +UUSOCL URC when the socket os closed by the remote +void processSocketClose(int socket) +{ + Serial.println(); + Serial.print(F("Socket ")); + Serial.print(socket); + Serial.println(F(" closed!")); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processPSDAction is provided to the SARA-R5 library via a +// callback setter -- setPSDActionCallback. (See setup()) +void processPSDAction(int result, IPAddress ip) +{ + Serial.println(); + Serial.print(F("PSD Action: result: ")); + Serial.print(String(result)); + if (result == 0) + Serial.print(F(" (success)")); + Serial.print(F(" IP Address: \"")); + Serial.print(String(ip[0])); + Serial.print(F(".")); + Serial.print(String(ip[1])); + Serial.print(F(".")); + Serial.print(String(ip[2])); + Serial.print(F(".")); + Serial.print(String(ip[3])); + Serial.println(F("\"")); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void setup() +{ + for (int i = 0; i < numConnections; i++) // Reset the ping and pong counts + { + pingCount[i] = 0; + pongCount[i] = 0; + } + + String currentOperator = ""; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("SARA-R5 Example")); + Serial.println(F("Wait until the SARA's NI LED lights up - then press any key to begin")); + + while (!Serial.available()) // Wait for the user to press a key (send any serial character) + ; + while (Serial.available()) // Empty the serial RX buffer + Serial.read(); + + //mySARA.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + // For the MicroMod Asset Tracker, we need to invert the power pin so it pulls high instead of low + // Comment the next line if required + mySARA.invertPowerPin(true); + + // Initialize the SARA + if (mySARA.begin(saraSerial, 115200) ) + { + Serial.println(F("SARA-R5 connected!")); + } + else + { + Serial.println(F("Unable to communicate with the SARA.")); + Serial.println(F("Manually power-on (hold the SARA On button for 3 seconds) on and try again.")); + while (1) ; // Loop forever on fail + } + Serial.println(); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // First check to see if we're connected to an operator: + if (mySARA.getOperator(¤tOperator) == SARA_R5_SUCCESS) + { + Serial.print(F("Connected to: ")); + Serial.println(currentOperator); + } + else + { + Serial.print(F("The SARA is not yet connected to an operator. Please use the previous examples to connect. Or wait and retry. Freezing...")); + while (1) + ; // Do nothing more + } + + // Deactivate the profile - in case one is already active + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_DEACTIVATE) != SARA_R5_SUCCESS) + { + Serial.println(F("Warning: performPDPaction (deactivate profile) failed. Probably because no profile was active.")); + } + + // Load the profile from NVM - these were saved by a previous example + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_LOAD) != SARA_R5_SUCCESS) + { + Serial.println(F("performPDPaction (load from NVM) failed! Freezing...")); + while (1) + ; // Do nothing more + } + + // Set a callback to process the results of the PSD Action - OPTIONAL + //mySARA.setPSDActionCallback(&processPSDAction); + + // Activate the profile + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_ACTIVATE) != SARA_R5_SUCCESS) + { + Serial.println(F("performPDPaction (activate profile) failed! Freezing...")); + while (1) + ; // Do nothing more + } + + //for (int i = 0; i < 100; i++) // Wait for up to a second for the PSD Action URC to arrive - OPTIONAL + //{ + // mySARA.bufferedPoll(); // Keep processing data from the SARA so we can process the PSD Action + // delay(10); + //} + + //Print the dynamic IP Address (for profile 0) + IPAddress myAddress; + mySARA.getNetworkAssignedIPAddress(0, &myAddress); + Serial.print(F("\r\nMy IP Address is: ")); + Serial.println(myAddress.toString()); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // Set a callback to process the socket listen + mySARA.setSocketListenCallback(&processSocketListen); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // Set a callback to process the socket data + mySARA.setSocketReadCallback(&processSocketData); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // Set a callback to process the socket close + // + // Note: the SARA-R5 only sends a +UUSOCL URC when the socket os closed by the remote + mySARA.setSocketCloseCallback(&processSocketClose); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + Serial.println(F("\r\nDo you want to Ping or Pong? (Always start the Pong first!)\r\n")); + Serial.println(F("1: Ping")); + Serial.println(F("2: Pong")); + Serial.println(F("\r\nPlease enter the number (followed by LF / Newline): ")); + + char c = 0; + bool selected = false; + int selection = 0; + while (!selected) + { + while (!Serial.available()) ; // Wait for a character to arrive + c = Serial.read(); // Read it + if (c == '\n') // Is it a LF? + { + if ((selection >= 1) && (selection <= 2)) + { + selected = true; + if (selection == 1) + Serial.println(F("\r\nPing selected!")); + else + Serial.println(F("\r\nPong selected!")); + } + else + { + Serial.println(F("Invalid choice. Please try again:")); + selection = 0; + } + } + else if ((c >= '0') && (c <= '9')) + { + selection = c - '0'; // Store a single digit + } + } + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + if (selection == 1) // Ping + { + iAmPing = true; + + Serial.println(F("\r\nPlease enter the IP number you want to ping (nnn.nnn.nnn.nnn followed by LF / Newline): ")); + + char c = 0; + bool selected = false; + int val = 0; + IPAddress theAddress = {0,0,0,0}; + int field = 0; + while (!selected) + { + while (!Serial.available()) ; // Wait for a character to arrive + c = Serial.read(); // Read it + if (c == '\n') // Is it a LF? + { + theAddress[field] = val; // Store the current value + if (field == 3) + selected = true; + else + { + Serial.println(F("Invalid IP Address. Please try again:")); + val = 0; + field = 0; + } + } + else if (c == '.') // Is it a separator + { + theAddress[field] = val; // Store the current value + if (field <= 2) + field++; // Increment the field + val = 0; // Reset the value + } + else if ((c >= '0') && (c <= '9')) + { + val *= 10; // Multiply by 10 + val += c - '0'; // Add the digit + } + } + + Serial.print(F("Remote address is ")); + Serial.println(theAddress.toString()); + + // Open the sockets + for (int i = 0; i < numConnections; i++) + { + + socketNum[i] = mySARA.socketOpen(SARA_R5_TCP); + if (socketNum[i] == -1) + { + Serial.println(F("socketOpen failed! Freezing...")); + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + + Serial.print(F("Connection ")); + Serial.print(i); + Serial.print(F(" is using socket ")); + Serial.println(socketNum[i]); + + // Connect to the remote IP Address + if (mySARA.socketConnect(socketNum[i], theAddress, TCP_PORT_BASE + i) != SARA_R5_SUCCESS) + { + Serial.println(F("socketConnect failed! Freezing...")); + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + else + { + Serial.println(F("Socket connected!")); + } + + // Send the first ping to start the ping-pong + const char ping[] = "Ping"; + mySARA.socketWrite(socketNum[i], ping); // Send the "Ping" + } + + } + + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + else // if (selection == 2) // Pong + { + iAmPing = false; + + // Open the sockets + for (int i = 0; i < numConnections; i++) + { + socketNum[i] = mySARA.socketOpen(SARA_R5_TCP); + if (socketNum[i] == -1) + { + Serial.println(F("socketOpen failed! Freezing...")); + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + + Serial.print(F("Connection ")); + Serial.print(i); + Serial.print(F(" is using socket ")); + Serial.println(socketNum[i]); + + // Start listening for a connection + if (mySARA.socketListen(socketNum[i], TCP_PORT_BASE + i) != SARA_R5_SUCCESS) + { + Serial.println(F("socketListen failed! Freezing...")); + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + } + } + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + startTime = millis(); // Record that start time + +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void loop() +{ + mySARA.bufferedPoll(); // Process the backlog (if any) and any fresh serial data + + for (int i = 0; i < numConnections; i++) + { + if (iAmPing) // Ping - close the socket when we've reached pingPongLimit + { + if ((pingCount[i] >= pingPongLimit) && (socketNum[i] >= 0)) + { + printSocketParameters(socketNum[i]); + + //Comment the next line if you want the remote to close the sockets when they timeout + //mySARA.socketClose(socketNum[i]); // Close the socket + + socketNum[i] = -1; + } + } + + else // Pong - close the socket when we've reached the timeLimit + { + if ((millis() > (startTime + timeLimit)) && (socketNum[i] >= 0)) + { + printSocketParameters(socketNum[i]); + + mySARA.socketClose(socketNum[i]); // Close the socket + + socketNum[i] = -1; + } + } + } +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// Print the socket parameters +// Note: the socket must be open. ERRORs will be returned if the socket is closed. +void printSocketParameters(int socket) +{ + Serial.print(F("\r\nSocket parameters for socket: ")); + Serial.println(socket); + + Serial.print(F("Socket type: ")); + SARA_R5_socket_protocol_t socketType; + mySARA.querySocketType(socket, &socketType); + if (socketType == SARA_R5_TCP) + Serial.println(F("TCP")); + else if (socketType == SARA_R5_UDP) + Serial.println(F("UDP")); + else + Serial.println(F("UNKNOWN! (Error!)")); + + Serial.print(F("Last error: ")); + int lastError; + mySARA.querySocketLastError(socket, &lastError); + Serial.println(lastError); + + Serial.print(F("Total bytes sent: ")); + uint32_t bytesSent; + mySARA.querySocketTotalBytesSent(socket, &bytesSent); + Serial.println(bytesSent); + + Serial.print(F("Total bytes received: ")); + uint32_t bytesReceived; + mySARA.querySocketTotalBytesReceived(socket, &bytesReceived); + Serial.println(bytesReceived); + + Serial.print(F("Remote IP Address: ")); + IPAddress remoteAddress; + int remotePort; + mySARA.querySocketRemoteIPAddress(socket, &remoteAddress, &remotePort); + Serial.println(remoteAddress.toString()); + + Serial.print(F("Socket status (TCP sockets only): ")); + SARA_R5_tcp_socket_status_t socketStatus; + mySARA.querySocketStatusTCP(socket, &socketStatus); + if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_INACTIVE) + Serial.println(F("INACTIVE")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_LISTEN) + Serial.println(F("LISTEN")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_SYN_SENT) + Serial.println(F("SYN_SENT")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_SYN_RCVD) + Serial.println(F("SYN_RCVD")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_ESTABLISHED) + Serial.println(F("ESTABLISHED")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_1) + Serial.println(F("FIN_WAIT_1")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_2) + Serial.println(F("FIN_WAIT_2")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_CLOSE_WAIT) + Serial.println(F("CLOSE_WAIT")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_CLOSING) + Serial.println(F("CLOSING")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_LAST_ACK) + Serial.println(F("LAST_ACK")); + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_TIME_WAIT) + Serial.println(F("TIME_WAIT")); + else + Serial.println(F("UNKNOWN! (Error!)")); + + Serial.print(F("Unacknowledged outgoing bytes: ")); + uint32_t bytesUnack; + mySARA.querySocketOutUnackData(socket, &bytesUnack); + Serial.println(bytesUnack); +} diff --git a/examples/SARA-R5_Example10_SocketPingPong_MultipleUDP/SARA-R5_Example10_SocketPingPong_MultipleUDP.ino b/examples/SARA-R5_Example10_SocketPingPong_MultipleUDP/SARA-R5_Example10_SocketPingPong_MultipleUDP.ino new file mode 100644 index 0000000..d85541a --- /dev/null +++ b/examples/SARA-R5_Example10_SocketPingPong_MultipleUDP/SARA-R5_Example10_SocketPingPong_MultipleUDP.ino @@ -0,0 +1,424 @@ +/* + + SARA-R5 Example + =============== + + Socket "Ping Pong" - Binary UDP Data Transfers on multiple sockets + + Written by: Paul Clark + Date: December 30th 2021 + + This example demonstrates how to ping-pong binary data from a SARA-R5 to UDP Echo Server using multiple UDP sockets. + + The PDP profile is read from NVM. Please make sure you have run examples 4 & 7 previously to set up the profile. + + The code asks for the IP Address to ping + The code then opens multiple UDP sockets using the base port number UDP_PORT_BASE + The code sends an initial "Ping" (Binary 0x00, 0x01, 0x02, 0x03) on all sockets using socketWriteUDP (+USOST) + The code polls continuously. When a +UUSORF URC message is received, data is read and passed to the socketReadCallbackPlus. + When "Pong" (Binary 0x04, 0x05, 0x06, 0x07) is received by the callback, the code sends "Ping" in reply + The Ping-Pong repeats 20 times + The socket is closed after the 20th Ping is sent + + This example needs an external UDP Echo Server. E.g. Python running on your home computer. + Here's a quick how-to (assuming you are familiar with Python): + Open up a Python editor on your computer + Copy the Multi_UDP_Echo.py from the GitHub repo Utils folder: https://github.com/sparkfun/SparkFun_u-blox_SARA-R5_Arduino_Library/tree/main/Utils + Log in to your router + Find your computer's local IP address (usually 192.168.0.something) + Go into your router's Security / Port Forwarding settings: + Create a new port forwarding rule + The IP address is your local IP address + Set the local port range to 1200-1206 (if you changed UDP_PORT_BASE, use that port number instead) + Set the external port range to 1200-1206 + Set the protocol to UDP (or BOTH) + Enable the rule + This will open up a direct connection from the outside world, through your router, to ports 1200-1206 on your computer + Remember to lock them down again when you're done! + Edit the Python code and change 'HOST' to your local IP number: + HOST = '192.168.0.nnn' + Run the Python code + Ask Google for your computer's public IP address: + Google "what is my IP address" + Run this code + Enter your computer's public IP address when asked + Sit back and watch the ping-pong! + The code will stop after 20 Pings+Echos and 20 Pongs+Echos on each port + On 5 ports, that's 400 UDP transfers in total! + + Feel like supporting open source hardware? + Buy a board from SparkFun! + + Licence: MIT + Please see LICENSE.md for full details + +*/ + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_SARA-R5_Arduino_Library + +// Uncomment the next line to connect to the SARA-R5 using hardware Serial1 +#define saraSerial Serial1 + +// Uncomment the next line to create a SoftwareSerial object to pass to the SARA-R5 library instead +//SoftwareSerial saraSerial(8, 9); + +// Create a SARA_R5 object to use throughout the sketch +// Usually we would tell the library which GPIO pin to use to control the SARA power (see below), +// but we can start the SARA without a power pin. It just means we need to manually +// turn the power on if required! ;-D +SARA_R5 mySARA; + +// Create a SARA_R5 object to use throughout the sketch +// We need to tell the library what GPIO pin is connected to the SARA power pin. +// If you're using the MicroMod Asset Tracker and the MicroMod Artemis Processor Board, +// the pin name is G2 which is connected to pin AD34. +// Change the pin number if required. +//SARA_R5 mySARA(34); + +// Create a SARA_R5 object to use throughout the sketch +// If you are using the LTE GNSS Breakout, and have access to the SARA's RESET_N pin, you can pass that to the library too +// allowing it to do an emergency shutdown if required. +// Change the pin numbers if required. +//SARA_R5 mySARA(34, 35); // PWR_ON, RESET_N + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +const int numConnections = 5; // How many sockets do you want to use? Max is 7. +unsigned int UDP_PORT_BASE = 1200; // Change this if required + +// Keep track of how many ping-pong exchanges have taken place. "Ping" closes the socket when pingCount reaches pingPongLimit. +volatile int pingCount[numConnections]; +volatile int pongCount[numConnections]; +const int pingPongLimit = 20; + +// Keep track of how long the sockets have been open. "Pong" closes the sockets when timeLimit (millis) is reached. +unsigned long startTime; +const unsigned long timeLimit = 120000; // 120 seconds + +#include // Needed for sockets +volatile int socketNum[numConnections]; // Record the socket numbers. -1 indicates the socket is invalid or closed. + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processSocketData is provided to the SARA-R5 library via a +// callback setter -- setSocketReadCallbackPlus. (See setup()) +void processSocketData(int socket, const char *theData, int length, IPAddress remoteAddress, int remotePort) +{ + int connection = -1; + for (int i = 0; i < numConnections; i++) + if (socketNum[i] == socket) + connection = i; + + if (connection == -1) + { + Serial.println(); + Serial.print(F("Data received on unexpected socket ")); + Serial.println(socket); + return; + } + + Serial.println(); + Serial.print(F("Data received on socket ")); + Serial.print(socket); + Serial.print(F(" from IP ")); + Serial.print(remoteAddress.toString()); + Serial.print(F(" using port ")); + Serial.print(remotePort); + Serial.print(F(" :")); + for (int i = 0; i < length; i++) + { + Serial.print(F(" 0x")); + if (theData[i] < 16) + Serial.print(F("0")); + Serial.print(theData[i], HEX); + } + Serial.println(); + + if ((theData[0] == 0x00) && (theData[1] == 0x01) && (theData[2] == 0x02) && (theData[3] == 0x03)) // Look for the "Ping" + { + if (pongCount[connection] < pingPongLimit) + { + const char pong[] = { 0x04, 0x05, 0x06, 0x07 }; + mySARA.socketWriteUDP(socket, remoteAddress, remotePort, pong, 4); // Send the "Pong" + pongCount[connection]++; + } + } + + if ((theData[0] == 0x04) && (theData[1] == 0x05) && (theData[2] == 0x06) && (theData[3] == 0x07)) // Look for the "Pong" + { + if (pingCount[connection] < pingPongLimit) + { + // Use the const char * version + //const char ping[] = { 0x00, 0x01, 0x02, 0x03 }; + //mySARA.socketWriteUDP(socket, remoteAddress, remotePort, ping, 4); // Send the "Ping" + + // Or use the String version. Both are OK for binary data. + String ping = ""; + ping.concat('\0'); // Construct the ping in a binary-friendly way + ping.concat('\1'); + ping.concat('\2'); + ping.concat('\3'); + mySARA.socketWriteUDP(socket, remoteAddress.toString(), remotePort, ping); // Send the "Ping" + + pingCount[connection]++; + } + } + + Serial.print(F("pingCount = ")); + Serial.print(pingCount[connection]); + Serial.print(F(" : pongCount = ")); + Serial.println(pongCount[connection]); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processSocketClose is provided to the SARA-R5 library via a +// callback setter -- setSocketCloseCallback. (See setup()) +// +// Note: the SARA-R5 only sends a +UUSOCL URC when the socket os closed by the remote +void processSocketClose(int socket) +{ + Serial.println(); + Serial.print(F("Socket ")); + Serial.print(socket); + Serial.println(F(" closed!")); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// processPSDAction is provided to the SARA-R5 library via a +// callback setter -- setPSDActionCallback. (See setup()) +void processPSDAction(int result, IPAddress ip) +{ + Serial.println(); + Serial.print(F("PSD Action: result: ")); + Serial.print(String(result)); + if (result == 0) + Serial.print(F(" (success)")); + Serial.print(F(" IP Address: \"")); + Serial.print(ip.toString()); + Serial.println(F("\"")); +} + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void setup() +{ + for (int i = 0; i < numConnections; i++) // Reset the ping and pong counts + { + pingCount[i] = 0; + pongCount[i] = 0; + } + + String currentOperator = ""; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("SARA-R5 Example")); + Serial.println(F("Wait until the SARA's NI LED lights up - then press any key to begin")); + + while (!Serial.available()) // Wait for the user to press a key (send any serial character) + ; + while (Serial.available()) // Empty the serial RX buffer + Serial.read(); + + //mySARA.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + // For the MicroMod Asset Tracker, we need to invert the power pin so it pulls high instead of low + // Comment the next line if required + mySARA.invertPowerPin(true); + + // Initialize the SARA + if (mySARA.begin(saraSerial, 115200) ) + { + Serial.println(F("SARA-R5 connected!")); + } + else + { + Serial.println(F("Unable to communicate with the SARA.")); + Serial.println(F("Manually power-on (hold the SARA On button for 3 seconds) on and try again.")); + while (1) ; // Loop forever on fail + } + Serial.println(); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // First check to see if we're connected to an operator: + if (mySARA.getOperator(¤tOperator) == SARA_R5_SUCCESS) + { + Serial.print(F("Connected to: ")); + Serial.println(currentOperator); + } + else + { + Serial.print(F("The SARA is not yet connected to an operator. Please use the previous examples to connect. Or wait and retry. Freezing...")); + while (1) + ; // Do nothing more + } + + // Deactivate the profile - in case one is already active + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_DEACTIVATE) != SARA_R5_SUCCESS) + { + Serial.println(F("Warning: performPDPaction (deactivate profile) failed. Probably because no profile was active.")); + } + + // Load the profile from NVM - these were saved by a previous example + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_LOAD) != SARA_R5_SUCCESS) + { + Serial.println(F("performPDPaction (load from NVM) failed! Freezing...")); + while (1) + ; // Do nothing more + } + + // Set a callback to process the results of the PSD Action - OPTIONAL + //mySARA.setPSDActionCallback(&processPSDAction); + + // Activate the profile + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_ACTIVATE) != SARA_R5_SUCCESS) + { + Serial.println(F("performPDPaction (activate profile) failed! Freezing...")); + while (1) + ; // Do nothing more + } + + //for (int i = 0; i < 100; i++) // Wait for up to a second for the PSD Action URC to arrive - OPTIONAL + //{ + // mySARA.bufferedPoll(); // Keep processing data from the SARA so we can process the PSD Action + // delay(10); + //} + + //Print the dynamic IP Address (for profile 0) + IPAddress myAddress; + mySARA.getNetworkAssignedIPAddress(0, &myAddress); + Serial.print(F("\r\nMy IP Address is: ")); + Serial.println(myAddress.toString()); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // Set a callback to process the socket data + mySARA.setSocketReadCallbackPlus(&processSocketData); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + // Set a callback to process the socket close + mySARA.setSocketCloseCallback(&processSocketClose); + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + Serial.println(F("\r\nPlease enter the IP number you want to ping (nnn.nnn.nnn.nnn followed by LF / Newline): ")); + + char c = 0; + bool selected = false; + int val = 0; + IPAddress theAddress = {0,0,0,0}; + int field = 0; + while (!selected) + { + while (!Serial.available()) ; // Wait for a character to arrive + c = Serial.read(); // Read it + if (c == '\n') // Is it a LF? + { + theAddress[field] = val; // Store the current value + if (field == 3) + selected = true; + else + { + Serial.println(F("Invalid IP Address. Please try again:")); + val = 0; + field = 0; + } + } + else if (c == '.') // Is it a separator + { + theAddress[field] = val; // Store the current value + if (field <= 2) + field++; // Increment the field + val = 0; // Reset the value + } + else if ((c >= '0') && (c <= '9')) + { + val *= 10; // Multiply by 10 + val += c - '0'; // Add the digit + } + } + + Serial.print(F("Remote address is ")); + Serial.println(theAddress.toString()); + + // Open the sockets + for (int i = 0; i < numConnections; i++) + { + + socketNum[i] = mySARA.socketOpen(SARA_R5_UDP); + if (socketNum[i] == -1) + { + Serial.println(F("socketOpen failed! Freezing...")); + while (1) + mySARA.bufferedPoll(); // Do nothing more except process any received data + } + + Serial.print(F("Connection ")); + Serial.print(i); + Serial.print(F(" is using socket ")); + Serial.println(socketNum[i]); + + // Send the first ping to start the ping-pong + const char ping[] = { 0x00, 0x01, 0x02, 0x03 }; + mySARA.socketWriteUDP(socketNum[i], theAddress, UDP_PORT_BASE + i, ping, 4); // Send the "Ping" + } + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + startTime = millis(); // Record that start time + +} // /setup() + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +void loop() +{ + mySARA.bufferedPoll(); // Process the backlog (if any) and any fresh serial data + + for (int i = 0; i < numConnections; i++) + { + if (((pingCount[i] >= pingPongLimit) || (millis() > (startTime + timeLimit))) && (socketNum[i] >= 0)) + { + printSocketParameters(socketNum[i]); + Serial.print(F("\r\nClosing socket ")); + Serial.println(socketNum[i]); + mySARA.socketClose(socketNum[i]); // Close the socket + socketNum[i] = -1; + } + } +} // /loop() + +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +// Print the socket parameters +// Note: the socket must be open. ERRORs will be returned if the socket is closed. +void printSocketParameters(int socket) +{ + Serial.print(F("\r\nSocket parameters for socket: ")); + Serial.println(socket); + + Serial.print(F("Socket type: ")); + SARA_R5_socket_protocol_t socketType; + mySARA.querySocketType(socket, &socketType); + if (socketType == SARA_R5_TCP) + Serial.println(F("TCP")); + else if (socketType == SARA_R5_UDP) + Serial.println(F("UDP")); + else + Serial.println(F("UNKNOWN! (Error!)")); + + Serial.print(F("Total bytes sent: ")); + uint32_t bytesSent; + mySARA.querySocketTotalBytesSent(socket, &bytesSent); + Serial.println(bytesSent); + + Serial.print(F("Total bytes received: ")); + uint32_t bytesReceived; + mySARA.querySocketTotalBytesReceived(socket, &bytesReceived); + Serial.println(bytesReceived); +} diff --git a/examples/SARA-R5_Example5_ReceiveSMS/SARA-R5_Example5_ReceiveSMS.ino b/examples/SARA-R5_Example5_ReceiveSMS/SARA-R5_Example5_ReceiveSMS.ino index 4eab6cb..94763f5 100644 --- a/examples/SARA-R5_Example5_ReceiveSMS/SARA-R5_Example5_ReceiveSMS.ino +++ b/examples/SARA-R5_Example5_ReceiveSMS/SARA-R5_Example5_ReceiveSMS.ino @@ -39,8 +39,6 @@ SARA_R5 mySARA; // Change the pin number if required. //SARA_R5 mySARA(34); -int previousUsed = 0; // Store the previous number of used memory locations - void setup() { String currentOperator = ""; @@ -87,10 +85,16 @@ void setup() while (1) ; // Do nothing more } + + while (Serial.available()) // Empty the serial RX buffer + Serial.read(); } void loop() { + static bool printReadMessages = true; // Print all messages once. Then only print new messages. Unless a message is deleted. + static int previousUsed = -1; // Store the previous number of used memory locations + // Read the number of used and total messages int used; int total; @@ -100,9 +104,9 @@ void loop() } else { - if (used > previousUsed) // Has a new message arrived? + if ((used != previousUsed) || printReadMessages) // Has a new message arrived? Or was the delete menu opened? { - Serial.print(F("Number of used memory locations: ")); + Serial.print(F("\r\nNumber of used memory locations: ")); Serial.println(used); Serial.print(F("Total number of memory locations: ")); Serial.println(total); @@ -118,11 +122,13 @@ void loop() String dateTime = ""; String message = ""; // Read the message from this location. Reading from empty message locations returns an ERROR + // unread can be: "REC UNREAD", "REC READ", "STO UNSENT", "STO SENT" + // If the location is empty, readSMSmessage will return a SARA_R5_ERROR_UNEXPECTED_RESPONSE if (mySARA.readSMSmessage(memoryLocation, &unread, &from, &dateTime, &message) == SARA_R5_SUCCESS) { - if (unread == "REC UNREAD") // Only print new (previously-unread) messages. Comment this line to display all messages. + if (printReadMessages || (unread == "REC UNREAD")) { - Serial.print(F("Message index: ")); + Serial.print(F("Message location: ")); Serial.println(memoryLocation); Serial.print(F("Status: ")); Serial.println(unread); @@ -138,12 +144,129 @@ void loop() memoryLocation++; // Move on to the next memory location } + printReadMessages = false; previousUsed = used; // Update previousUsed Serial.println(F("Waiting for a new message...")); Serial.println(); + Serial.println(F("Hit any key to delete a message...")); + Serial.println(); } } - delay(5000); // Check for new messages every 5 seconds + int delayCount = 0; + while (delayCount < 5000) + { + delay(1); // Delay for five seconds, unless the user presses a key + delayCount++; + + if (Serial.available()) + { + Serial.println(F("To delete a single message: enter its location followed by LF / Newline")); + Serial.println(F("To delete all read messages: enter r followed by LF / Newline")); + Serial.println(F("To delete all read and sent messages: enter s followed by LF / Newline")); + Serial.println(F("To delete all read, sent and unsent messages: enter u followed by LF / Newline")); + Serial.println(F("To delete all messages, including unread messages: enter a followed by LF / Newline")); + Serial.println(F("To exit: enter LF / Newline")); + + Serial.read(); // Read and discard the char that opened the menu + + int location = 0; + bool selected = false; + while (!selected) + { + while (!Serial.available()) ; // Wait for a character to arrive + char c = Serial.read(); // Read it + if (c == '\n') // Is it a LF? + { + if ((location >= 1) && (location <= total)) // Delete a single message at location + { + if (mySARA.deleteSMSmessage(location) == SARA_R5_SUCCESS) + { + Serial.println(F("\r\nMessage deleted!\r\n")); + printReadMessages = true; + } + else + { + Serial.println(F("\r\nMessage not deleted!\r\n")); + } + } + else if (location == 1001) // r + { + if (mySARA.deleteReadSMSmessages() == SARA_R5_SUCCESS) + { + Serial.println(F("\r\nRead messages deleted!\r\n")); + printReadMessages = true; + } + else + { + Serial.println(F("\r\nMessages not deleted!\r\n")); + } + } + else if (location == 1002) // s + { + if (mySARA.deleteReadSentSMSmessages() == SARA_R5_SUCCESS) + { + Serial.println(F("\r\nRead and sent messages deleted!\r\n")); + printReadMessages = true; + } + else + { + Serial.println(F("\r\nMessages not deleted!\r\n")); + } + } + else if (location == 1003) // u + { + if (mySARA.deleteReadSentUnsentSMSmessages() == SARA_R5_SUCCESS) + { + Serial.println(F("\r\nRead, sent and unsent messages deleted!\r\n")); + printReadMessages = true; + } + else + { + Serial.println(F("\r\nMessages not deleted!\r\n")); + } + } + else if (location == 1004) // a + { + if (mySARA.deleteAllSMSmessages() == SARA_R5_SUCCESS) + { + Serial.println(F("\r\nAll messages deleted!\r\n")); + printReadMessages = true; + } + else + { + Serial.println(F("\r\nMessages not deleted!\r\n")); + } + } + else + Serial.println(F("\r\nExit...\r\n")); + selected = true; + } + else if ((c >= '0') && (c <= '9')) + { + location *= 10; // Multiply by 10 + location += c - '0'; // Add the digit + } + else if (c == 'r') + { + location = 1001; + } + else if (c == 's') + { + location = 1002; + } + else if (c == 'u') + { + location = 1003; + } + else if (c == 'a') + { + location = 1004; + } + } + + delayCount = 5000; + } + } } diff --git a/keywords.txt b/keywords.txt index 87e1598..a3d07c6 100644 --- a/keywords.txt +++ b/keywords.txt @@ -51,6 +51,7 @@ processReadEvent KEYWORD2 poll KEYWORD2 setSocketListenCallback KEYWORD2 setSocketReadCallback KEYWORD2 +setSocketReadCallbackPlus KEYWORD2 setSocketCloseCallback KEYWORD2 setGpsReadCallback KEYWORD2 setSIMstateReportCallback KEYWORD2 @@ -96,6 +97,11 @@ setSMSMessageFormat KEYWORD2 sendSMS KEYWORD2 getPreferredMessageStorage KEYWORD2 readSMSmessage KEYWORD2 +deleteSMSmessage KEYWORD2 +deleteReadSMSmessages KEYWORD2 +deleteReadSentSMSmessages KEYWORD2 +deleteReadSentUnsentSMSmessages KEYWORD2 +deleteAllSMSmessages KEYWORD2 setBaud KEYWORD2 setFlowControl KEYWORD2 setGpioMode KEYWORD2 @@ -106,13 +112,22 @@ socketConnect KEYWORD2 socketWrite KEYWORD2 socketWriteUDP KEYWORD2 socketRead KEYWORD2 +socketReadAvailable KEYWORD2 socketReadUDP KEYWORD2 +socketReadAvailableUDP KEYWORD2 socketListen KEYWORD2 socketDirectLinkMode KEYWORD2 socketDirectLinkTimeTrigger KEYWORD2 socketDirectLinkDataLengthTrigger KEYWORD2 socketDirectLinkCharacterTrigger KEYWORD2 socketDirectLinkCongestionTimer KEYWORD2 +querySocketType KEYWORD2 +querySocketLastError KEYWORD2 +querySocketTotalBytesSent KEYWORD2 +querySocketTotalBytesReceived KEYWORD2 +querySocketRemoteIPAddress KEYWORD2 +querySocketStatusTCP KEYWORD2 +querySocketOutUnackData KEYWORD2 socketGetLastError KEYWORD2 lastRemoteIP KEYWORD2 ping KEYWORD2 @@ -130,6 +145,7 @@ sendHTTPPOSTdata KEYWORD2 setPDPconfiguration KEYWORD2 performPDPaction KEYWORD2 activatePDPcontext KEYWORD2 +getNetworkAssignedIPAddress KEYWORD2 isGPSon KEYWORD2 gpsPower KEYWORD2 # gpsEnableClock KEYWORD2 @@ -336,6 +352,17 @@ EXT_GNSS_TIMESTAMP LITERAL1 DTR_MODE LITERAL1 KHZ_32768_OUT LITERAL1 PAD_DISABLED LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_INACTIVE LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_LISTEN LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_SYN_SENT LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_SYN_RCVD LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_ESTABLISHED LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_1 LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_2 LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_CLOSE_WAIT LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_CLOSING LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_LAST_ACK LITERAL1 +SARA_R5_TCP_SOCKET_STATUS_TIME_WAIT LITERAL1 GNSS_SYSTEM_GPS LITERAL1 GNSS_SYSTEM_SBAS LITERAL1 GNSS_SYSTEM_GALILEO LITERAL1 diff --git a/library.properties b/library.properties index 6156001..4d6f0f8 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SparkFun u-blox SARA-R5 Arduino Library -version=1.0.4 +version=1.0.5 author=SparkFun Electronics maintainer=SparkFun Electronics sentence=Library for the u-blox SARA-R5 LTE-M / NB-IoT modules with secure cloud diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index 0e58270..9687997 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -29,6 +29,7 @@ SARA_R5::SARA_R5(int powerPin, int resetPin, uint8_t maxInitDepth) _maxInitDepth = maxInitDepth; _socketListenCallback = NULL; _socketReadCallback = NULL; + _socketReadCallbackPlus = NULL; _socketCloseCallback = NULL; _gpsRequestCallback = NULL; _simStateReportCallback = NULL; @@ -37,9 +38,13 @@ SARA_R5::SARA_R5(int powerPin, int resetPin, uint8_t maxInitDepth) _httpCommandRequestCallback = NULL; _lastRemoteIP = {0, 0, 0, 0}; _lastLocalIP = {0, 0, 0, 0}; + for (int i = 0; i < SARA_R5_NUM_SOCKETS; i++) + _lastSocketProtocol[i] = 0; // Set to zero initially. Will be set to TCP/UDP by socketOpen etc. - memset(saraRXBuffer, 0, RXBuffSize); - memset(saraResponseBacklog, 0, RXBuffSize); + memset(_saraRXBuffer, 0, _RXBuffSize); + memset(_pruneBuffer, 0, _RXBuffSize); + memset(_saraResponseBacklog, 0, _RXBuffSize); + _saraResponseBacklogLength = 0; } #ifdef SARA_R5_SOFTWARE_SERIAL_ENABLED @@ -89,67 +94,97 @@ bool SARA_R5::bufferedPoll(void) int avail = 0; char c = 0; bool handled = false; - unsigned long timeIn = micros(); + unsigned long timeIn = millis(); char *event; + int backlogLen = _saraResponseBacklogLength; - memset(saraRXBuffer, 0, RXBuffSize); // Clear saraRXBuffer + memset(_saraRXBuffer, 0, _RXBuffSize); // Clear _saraRXBuffer - int backlogLen = strlen(saraResponseBacklog); // Check how many bytes are in the backlog - - // Does the backlog contain any data? If it does, copy it into saraRXBuffer and then clear the backlog - if (backlogLen > 0) + // Does the backlog contain any data? If it does, copy it into _saraRXBuffer and then clear the backlog + if (_saraResponseBacklogLength > 0) { //The backlog also logs reads from other tasks like transmitting. if (_printDebug == true) - _debugPort->println(F("bufferedPoll: backlog found!")); - memcpy(saraRXBuffer + avail, saraResponseBacklog, backlogLen); - avail += backlogLen; - memset(saraResponseBacklog, 0, RXBuffSize); // Clear the backlog making sure it is NULL-terminated + { + _debugPort->print(F("bufferedPoll: backlog found! backlogLen is ")); + _debugPort->println(_saraResponseBacklogLength); + } + memcpy(_saraRXBuffer + avail, _saraResponseBacklog, _saraResponseBacklogLength); + avail += _saraResponseBacklogLength; + memset(_saraResponseBacklog, 0, _RXBuffSize); // Clear the backlog making sure it is NULL-terminated + _saraResponseBacklogLength = 0; } if ((hwAvailable() > 0) || (backlogLen > 0)) // If either new data is available, or backlog had data. { - // Wait for up to rxWindowUS for new serial data to arrive. - while (((micros() - timeIn) < rxWindowUS) && (avail < RXBuffSize)) + // Wait for up to _rxWindowMillis for new serial data to arrive. + while (((millis() - timeIn) < _rxWindowMillis) && (avail < _RXBuffSize)) { if (hwAvailable() > 0) //hwAvailable can return -1 if the serial port is NULL { c = readChar(); - saraRXBuffer[avail++] = c; - timeIn = micros(); + // bufferedPoll is only interested in the URCs. + // The URCs are all readable. + // strtok does not like NULL characters. + // So we need to make sure no NULL characters are added to _saraRXBuffer + if (c == '\0') + c = '0'; // Convert any NULLs to ASCII Zeros + _saraRXBuffer[avail++] = c; + timeIn = millis(); } } - // saraRXBuffer now contains the backlog (if any) and the new serial data (if any) + // _saraRXBuffer now contains the backlog (if any) and the new serial data (if any) + + // A health warning about strtok: + // strtok will convert any delimiters it finds ("\r\n" in our case) into NULL characters. + // Also, be very careful that you do not use strtok within an strtok while loop. + // The next call of strtok(NULL, ...) in the outer loop will use the pointer saved from the inner loop! + // In our case, strtok is also used in pruneBacklog, which is called by waitForRespone or sendCommandWithResponse, + // which is called by the parse functions called by processURCEvent... + // The solution is to use strtok_r - the reentrant version of strtok - event = strtok(saraRXBuffer, "\r\n"); // Look for an 'event' (saraRXBuffer contains something ending in \r\n) + char *preservedEvent; + event = strtok_r(_saraRXBuffer, "\r\n", &preservedEvent); // Look for an 'event' (_saraRXBuffer contains something ending in \r\n) + + if (event != NULL) + if (_printDebug == true) + _debugPort->println(F("bufferedPoll: event(s) found! ===>")); while (event != NULL) // Keep going until all events have been processed { if (_printDebug == true) - _debugPort->print(F("bufferedPoll: event: ")); - if (_printDebug == true) + { + _debugPort->print(F("bufferedPoll: start of event: ")); _debugPort->println(event); + } //Process the event bool latestHandled = processURCEvent((const char *)event); if (latestHandled) handled = true; // handled will be true if latestHandled has ever been true - backlogLen = strlen(saraResponseBacklog); // Has any new data been added to the backlog? - if ((backlogLen > 0) && ((avail + backlogLen) < RXBuffSize)) + if ((_saraResponseBacklogLength > 0) && ((avail + _saraResponseBacklogLength) < _RXBuffSize)) // Has any new data been added to the backlog? { if (_printDebug == true) + { _debugPort->println(F("bufferedPoll: new backlog added!")); - memcpy(saraRXBuffer + avail, saraResponseBacklog, backlogLen); - avail += backlogLen; - memset(saraResponseBacklog, 0, RXBuffSize); //Clear out backlog buffer. Again. + } + memcpy(_saraRXBuffer + avail, _saraResponseBacklog, _saraResponseBacklogLength); + avail += _saraResponseBacklogLength; + memset(_saraResponseBacklog, 0, _RXBuffSize); //Clear out the backlog buffer again. + _saraResponseBacklogLength = 0; } //Walk through any remaining events - event = strtok(NULL, "\r\n"); + event = strtok_r(NULL, "\r\n", &preservedEvent); + if (_printDebug == true) _debugPort->println(F("bufferedPoll: end of event")); //Just to denote end of processing event. + + if (event == NULL) + if (_printDebug == true) + _debugPort->println(F("bufferedPoll: <=== end of event(s)!")); } } @@ -157,20 +192,35 @@ bool SARA_R5::bufferedPoll(void) return handled; } +// Parse incoming URC's - the associated parse functions pass the data to the user via the callbacks (if defined) bool SARA_R5::processURCEvent(const char *event) { - { + { // URC: +UUSORD (Read Socket Data) int socket, length; int ret = sscanf(event, "+UUSORD: %d,%d", &socket, &length); if (ret == 2) { if (_printDebug == true) _debugPort->println(F("processReadEvent: read socket data")); - parseSocketReadIndication(socket, length); + // From the SARA_R5 AT Commands Manual: + // "For the UDP socket type the URC +UUSORD: , notifies that a UDP packet has been received, + // either when buffer is empty or after a UDP packet has been read and one or more packets are stored in the + // buffer." + // So we need to check if this is a TCP socket or a UDP socket: + // If UDP, we call parseSocketReadIndicationUDP. + // Otherwise, we call parseSocketReadIndication. + if (_lastSocketProtocol[socket] == SARA_R5_UDP) + { + if (_printDebug == true) + _debugPort->println(F("processReadEvent: received +UUSORD but socket is UDP. Calling parseSocketReadIndicationUDP")); + parseSocketReadIndicationUDP(socket, length); + } + else + parseSocketReadIndication(socket, length); return true; } } - { + { // URC: +UUSORF (Receive From command (UDP only)) int socket, length; int ret = sscanf(event, "+UUSORF: %d,%d", &socket, &length); if (ret == 2) @@ -181,7 +231,7 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - { + { // URC: +UUSOLI (Set Listening Socket) int socket = 0; int listenSocket = 0; unsigned int port = 0; @@ -200,10 +250,12 @@ bool SARA_R5::processURCEvent(const char *event) &listenPort); for (int i = 0; i <= 3; i++) { - remoteIP[i] = (uint8_t)remoteIPstore[i]; - localIP[i] = (uint8_t)localIPstore[i]; + if (ret >= 5) + remoteIP[i] = (uint8_t)remoteIPstore[i]; + if (ret >= 11) + localIP[i] = (uint8_t)localIPstore[i]; } - if (ret > 4) + if (ret >= 5) { if (_printDebug == true) _debugPort->println(F("processReadEvent: socket listen")); @@ -211,7 +263,7 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - { + { // URC: +UUSOCL (Close Socket) int socket; int ret = sscanf(event, "+UUSOCL: %d", &socket); if (ret == 1) @@ -228,7 +280,7 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - { + { // URC: +UULOC (Localization information - CellLocate and hybrid positioning) ClockData clck; PositionData gps; SpeedData spd; @@ -299,12 +351,12 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - { + { // URC: +UUSIMSTAT (SIM Status) SARA_R5_sim_states_t state; int scanNum; int stateStore; - scanNum = sscanf(event, "+UUSIMSTAT:%d", &stateStore); + scanNum = sscanf(event, "+UUSIMSTAT:%d", &stateStore); // Note: no space after the colon! if (scanNum == 1) { @@ -321,7 +373,7 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - { + { // URC: +UUPSDA (Packet Switched Data Action) int result; IPAddress remoteIP = {0, 0, 0, 0}; int scanNum; @@ -348,7 +400,7 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - { + { // URC: +UUHTTPCR (HTTP Command Result) int profile, command, result; int scanNum; @@ -370,8 +422,7 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - // Save UUPING until last as it probably has the most chance of going wrong? - { + { // URC: +UUPING (Ping Result) int retry = 0; int p_size = 0; int ttl = 0; @@ -432,28 +483,28 @@ bool SARA_R5::poll(void) char c = 0; bool handled = false; - memset(saraRXBuffer, 0, RXBuffSize); // Clear saraRXBuffer + memset(_saraRXBuffer, 0, _RXBuffSize); // Clear _saraRXBuffer if (hwAvailable() > 0) //hwAvailable can return -1 if the serial port is NULL { - while (c != '\n') // Copy characters into saraRXBuffer. Stop at the first new line + while (c != '\n') // Copy characters into _saraRXBuffer. Stop at the first new line { if (hwAvailable() > 0) //hwAvailable can return -1 if the serial port is NULL { c = readChar(); - saraRXBuffer[avail++] = c; + _saraRXBuffer[avail++] = c; } } // Now search for all supported URC's - handled = processURCEvent(saraRXBuffer); + handled = processURCEvent(_saraRXBuffer); - if ((handled == false) && (strlen(saraRXBuffer) > 2)) + if ((handled == false) && (strlen(_saraRXBuffer) > 2)) { if (_printDebug == true) { _debugPort->print(F("poll: ")); - _debugPort->println(saraRXBuffer); + _debugPort->println(_saraRXBuffer); } } else @@ -473,6 +524,11 @@ void SARA_R5::setSocketReadCallback(void (*socketReadCallback)(int, String)) _socketReadCallback = socketReadCallback; } +void SARA_R5::setSocketReadCallbackPlus(void (*socketReadCallbackPlus)(int, const char *, int, IPAddress, int)) // socket, data, length, remoteAddress, remotePort +{ + _socketReadCallbackPlus = socketReadCallbackPlus; +} + void SARA_R5::setSocketCloseCallback(void (*socketCloseCallback)(int)) { _socketCloseCallback = socketCloseCallback; @@ -536,18 +592,14 @@ size_t SARA_R5::write(const char *str) size_t SARA_R5::write(const char *buffer, size_t size) { - //size is unused at the moment but could be used if this function is ever updated to use write instead of print. - size_t ignoreMe = size; - ignoreMe -= 0; // Avoid the pesky compiler warning. - if (_hardSerial != NULL) { - return _hardSerial->print(buffer); + return _hardSerial->write((const uint8_t *)buffer, (int)size); } #ifdef SARA_R5_SOFTWARE_SERIAL_ENABLED else if (_softSerial != NULL) { - return _softSerial->print(buffer); + return _softSerial->write((const uint8_t *)buffer, (int)size); } #endif return (size_t)0; @@ -593,7 +645,7 @@ String SARA_R5::getManufacturerID(void) char idResponse[16] = {0x00}; // E.g. u-blox SARA_R5_error_t err; - response = sara_r5_calloc_char(sizeof(idResponse) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); err = sendCommandWithResponse(SARA_R5_COMMAND_MANU_ID, SARA_R5_RESPONSE_OK, response, SARA_R5_STANDARD_RESPONSE_TIMEOUT); @@ -614,7 +666,7 @@ String SARA_R5::getModelID(void) char idResponse[16] = {0x00}; // E.g. SARA-R510M8Q SARA_R5_error_t err; - response = sara_r5_calloc_char(sizeof(idResponse) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); err = sendCommandWithResponse(SARA_R5_COMMAND_MODEL_ID, SARA_R5_RESPONSE_OK, response, SARA_R5_STANDARD_RESPONSE_TIMEOUT); @@ -635,7 +687,7 @@ String SARA_R5::getFirmwareVersion(void) char idResponse[16] = {0x00}; // E.g. 11.40 SARA_R5_error_t err; - response = sara_r5_calloc_char(sizeof(idResponse) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); err = sendCommandWithResponse(SARA_R5_COMMAND_FW_VER_ID, SARA_R5_RESPONSE_OK, response, SARA_R5_STANDARD_RESPONSE_TIMEOUT); @@ -656,7 +708,7 @@ String SARA_R5::getSerialNo(void) char idResponse[16] = {0x00}; // E.g. 357520070120767 SARA_R5_error_t err; - response = sara_r5_calloc_char(sizeof(idResponse) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); err = sendCommandWithResponse(SARA_R5_COMMAND_SERIAL_NO, SARA_R5_RESPONSE_OK, response, SARA_R5_STANDARD_RESPONSE_TIMEOUT); @@ -677,7 +729,7 @@ String SARA_R5::getIMEI(void) char imeiResponse[16] = {0x00}; // E.g. 004999010640000 SARA_R5_error_t err; - response = sara_r5_calloc_char(sizeof(imeiResponse) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); err = sendCommandWithResponse(SARA_R5_COMMAND_IMEI, SARA_R5_RESPONSE_OK, response, SARA_R5_STANDARD_RESPONSE_TIMEOUT); @@ -698,7 +750,7 @@ String SARA_R5::getIMSI(void) char imsiResponse[16] = {0x00}; // E.g. 222107701772423 SARA_R5_error_t err; - response = sara_r5_calloc_char(sizeof(imsiResponse) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); err = sendCommandWithResponse(SARA_R5_COMMAND_IMSI, SARA_R5_RESPONSE_OK, response, SARA_R5_STANDARD_RESPONSE_TIMEOUT); @@ -719,7 +771,7 @@ String SARA_R5::getCCID(void) char ccidResponse[21] = {0x00}; // E.g. +CCID: 8939107900010087330 SARA_R5_error_t err; - response = sara_r5_calloc_char(sizeof(ccidResponse) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); err = sendCommandWithResponse(SARA_R5_COMMAND_CCID, SARA_R5_RESPONSE_OK, response, SARA_R5_STANDARD_RESPONSE_TIMEOUT); @@ -740,7 +792,7 @@ String SARA_R5::getSubscriberNo(void) char idResponse[128] = {0x00}; // E.g. +CNUM: "ABCD . AAA","123456789012",129 SARA_R5_error_t err; - response = sara_r5_calloc_char(sizeof(idResponse) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); err = sendCommandWithResponse(SARA_R5_COMMAND_CNUM, SARA_R5_RESPONSE_OK, response, SARA_R5_10_SEC_TIMEOUT); @@ -761,7 +813,7 @@ String SARA_R5::getCapabilities(void) char idResponse[128] = {0x00}; // E.g. +GCAP: +FCLASS, +CGSM SARA_R5_error_t err; - response = sara_r5_calloc_char(sizeof(idResponse) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); err = sendCommandWithResponse(SARA_R5_COMMAND_REQ_CAP, SARA_R5_RESPONSE_OK, response, SARA_R5_STANDARD_RESPONSE_TIMEOUT); @@ -813,7 +865,7 @@ String SARA_R5::clock(void) return ""; sprintf(command, "%s?", SARA_R5_COMMAND_CLOCK); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -862,6 +914,7 @@ SARA_R5_error_t SARA_R5::clock(uint8_t *y, uint8_t *mo, uint8_t *d, char *command; char *response; char tzPlusMinus; + int scanNum = 0; int iy, imo, id, ih, imin, is, itz; @@ -870,7 +923,7 @@ SARA_R5_error_t SARA_R5::clock(uint8_t *y, uint8_t *mo, uint8_t *d, return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s?", SARA_R5_COMMAND_CLOCK); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -883,8 +936,11 @@ SARA_R5_error_t SARA_R5::clock(uint8_t *y, uint8_t *mo, uint8_t *d, // Response format (if TZ is negative): \r\n+CCLK: "YY/MM/DD,HH:MM:SS-TZ"\r\n\r\nOK\r\n if (err == SARA_R5_ERROR_SUCCESS) { - if (sscanf(response, "\r\n+CCLK: \"%d/%d/%d,%d:%d:%d%c%d\"\r\n", - &iy, &imo, &id, &ih, &imin, &is, &tzPlusMinus, &itz) == 8) + char *searchPtr = strstr(response, "+CCLK: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+CCLK: \"%d/%d/%d,%d:%d:%d%c%d\"\r\n", + &iy, &imo, &id, &ih, &imin, &is, &tzPlusMinus, &itz); + if (scanNum == 8) { *y = iy; *mo = imo; @@ -892,10 +948,10 @@ SARA_R5_error_t SARA_R5::clock(uint8_t *y, uint8_t *mo, uint8_t *d, *h = ih; *min = imin; *s = is; - if (tzPlusMinus == '+') - *tz = itz; - else + if (tzPlusMinus == '-') *tz = 0 - itz; + else + *tz = itz; } else err = SARA_R5_ERROR_UNEXPECTED_RESPONSE; @@ -939,7 +995,7 @@ SARA_R5_error_t SARA_R5::getUtimeMode(SARA_R5_utime_mode_t *mode, SARA_R5_utime_ return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s?", SARA_R5_GNSS_REQUEST_TIME); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -952,8 +1008,10 @@ SARA_R5_error_t SARA_R5::getUtimeMode(SARA_R5_utime_mode_t *mode, SARA_R5_utime_ // Response format: \r\n+UTIME: [,]\r\n\r\nOK\r\n if (err == SARA_R5_ERROR_SUCCESS) { - int mStore, sStore; - int scanned = sscanf(response, "\r\n+UTIME: %d,%d\r\n", &mStore, &sStore); + int mStore, sStore, scanned = 0; + char *searchPtr = strstr(response, "+UTIME: "); + if (searchPtr != NULL) + scanned = sscanf(searchPtr, "+UTIME: %d,%d\r\n", &mStore, &sStore); m = (SARA_R5_utime_mode_t)mStore; s = (SARA_R5_utime_sensor_t)sStore; if (scanned == 2) @@ -1004,7 +1062,7 @@ SARA_R5_error_t SARA_R5::getUtimeIndication(SARA_R5_utime_urc_configuration_t *c return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s?", SARA_R5_GNSS_TIME_INDICATION); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -1017,8 +1075,10 @@ SARA_R5_error_t SARA_R5::getUtimeIndication(SARA_R5_utime_urc_configuration_t *c // Response format: \r\n+UTIMEIND: \r\n\r\nOK\r\n if (err == SARA_R5_ERROR_SUCCESS) { - int cStore; - int scanned = sscanf(response, "\r\n+UTIMEIND: %d\r\n", &cStore); + int cStore, scanned = 0; + char *searchPtr = strstr(response, "+UTIMEIND: "); + if (searchPtr != NULL) + scanned = sscanf(searchPtr, "+UTIMEIND: %d\r\n", &cStore); c = (SARA_R5_utime_urc_configuration_t)cStore; if (scanned == 1) { @@ -1067,7 +1127,7 @@ SARA_R5_error_t SARA_R5::getUtimeConfiguration(int32_t *offsetNanoseconds, int32 return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s?", SARA_R5_GNSS_TIME_CONFIGURATION); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -1080,10 +1140,13 @@ SARA_R5_error_t SARA_R5::getUtimeConfiguration(int32_t *offsetNanoseconds, int32 // Response format: \r\n+UTIMECFG: ,\r\n\r\nOK\r\n if (err == SARA_R5_ERROR_SUCCESS) { + int scanned = 0; + char *searchPtr = strstr(response, "+UTIMECFG: "); + if (searchPtr != NULL) #if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) - int scanned = sscanf(response, "\r\n+UTIMECFG: %d,%d\r\n", &ons, &os); + scanned = sscanf(searchPtr, "+UTIMECFG: %d,%d\r\n", &ons, &os); #else - int scanned = sscanf(response, "\r\n+UTIMECFG: %ld,%ld\r\n", &ons, &os); + scanned = sscanf(searchPtr, "+UTIMECFG: %ld,%ld\r\n", &ons, &os); #endif if (scanned == 2) { @@ -1127,7 +1190,7 @@ int8_t SARA_R5::rssi(void) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s", SARA_R5_SIGNAL_QUALITY); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -1135,7 +1198,8 @@ int8_t SARA_R5::rssi(void) } err = sendCommandWithResponse(command, - SARA_R5_RESPONSE_OK, response, 10000, AT_COMMAND); + SARA_R5_RESPONSE_OK, response, 10000, + minimumResponseAllocation, AT_COMMAND); if (err != SARA_R5_ERROR_SUCCESS) { free(command); @@ -1143,7 +1207,11 @@ int8_t SARA_R5::rssi(void) return -1; } - if (sscanf(response, "\r\n+CSQ: %d,%*d", &rssi) != 1) + int scanned = 0; + char *searchPtr = strstr(response, "+CSQ: "); + if (searchPtr != NULL) + scanned = sscanf(searchPtr, "+CSQ: %d,%*d", &rssi); + if (scanned != 1) { rssi = -1; } @@ -1165,7 +1233,7 @@ SARA_R5_registration_status_t SARA_R5::registration(void) return SARA_R5_REGISTRATION_INVALID; sprintf(command, "%s?", SARA_R5_REGISTRATION_STATUS); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -1173,7 +1241,8 @@ SARA_R5_registration_status_t SARA_R5::registration(void) } err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, - response, SARA_R5_STANDARD_RESPONSE_TIMEOUT, AT_COMMAND); + response, SARA_R5_STANDARD_RESPONSE_TIMEOUT, + minimumResponseAllocation, AT_COMMAND); if (err != SARA_R5_ERROR_SUCCESS) { free(command); @@ -1181,10 +1250,13 @@ SARA_R5_registration_status_t SARA_R5::registration(void) return SARA_R5_REGISTRATION_INVALID; } - if (sscanf(response, "\r\n+CREG: %*d,%d", &status) != 1) - { + int scanned = 0; + char *searchPtr = strstr(response, "+CREG: "); + if (searchPtr != NULL) + scanned = sscanf(searchPtr, "+CREG: %*d,%d", &status); + if (scanned != 1) status = SARA_R5_REGISTRATION_INVALID; - } + free(command); free(response); return (SARA_R5_registration_status_t)status; @@ -1261,13 +1333,13 @@ SARA_R5_error_t SARA_R5::setAPN(String apn, uint8_t cid, SARA_R5_pdp_type pdpTyp memcpy(pdpStr, "IP", 2); break; case PDP_TYPE_NONIP: - memcpy(pdpStr, "NONIP", 2); + memcpy(pdpStr, "NONIP", 5); break; case PDP_TYPE_IPV4V6: - memcpy(pdpStr, "IPV4V6", 2); + memcpy(pdpStr, "IPV4V6", 6); break; case PDP_TYPE_IPV6: - memcpy(pdpStr, "IPV6", 2); + memcpy(pdpStr, "IPV6", 4); break; default: free(command); @@ -1325,7 +1397,7 @@ SARA_R5_error_t SARA_R5::getAPN(int cid, String *apn, IPAddress *ip) } err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, - SARA_R5_STANDARD_RESPONSE_TIMEOUT); + SARA_R5_STANDARD_RESPONSE_TIMEOUT, 1024); if (err == SARA_R5_ERROR_SUCCESS) { @@ -1440,7 +1512,7 @@ SARA_R5_error_t SARA_R5::getSIMstateReportingMode(int *mode) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s?", SARA_R5_SIM_STATE); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -1452,7 +1524,10 @@ SARA_R5_error_t SARA_R5::getSIMstateReportingMode(int *mode) if (err == SARA_R5_ERROR_SUCCESS) { - int scanned = sscanf(response, "\r\n+USIMSTAT: %d\r\n", &m); + int scanned = 0; + char *searchPtr = strstr(response, "+USIMSTAT: "); + if (searchPtr != NULL) + scanned = sscanf(searchPtr, "+USIMSTAT: %d\r\n", &m); if (scanned == 1) { *mode = m; @@ -1519,7 +1594,8 @@ uint8_t SARA_R5::getOperators(struct operator_stats *opRet, int maxOps) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=?", SARA_R5_OPERATOR_SELECTION); - response = sara_r5_calloc_char((maxOps + 1) * 48); + int responseSize = (maxOps + 1) * 48; + response = sara_r5_calloc_char(responseSize); if (response == NULL) { free(command); @@ -1528,7 +1604,7 @@ uint8_t SARA_R5::getOperators(struct operator_stats *opRet, int maxOps) // AT+COPS maximum response time is 3 minutes (180000 ms) err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, - SARA_R5_3_MIN_TIMEOUT); + SARA_R5_3_MIN_TIMEOUT, responseSize); // Sample responses: // +COPS: (3,"Verizon Wireless","VzW","311480",8),,(0,1,2,3,4),(0,1,2) @@ -1638,7 +1714,7 @@ SARA_R5_error_t SARA_R5::getOperator(String *oper) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s?", SARA_R5_OPERATOR_SELECTION); - response = sara_r5_calloc_char(64); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -1681,7 +1757,6 @@ SARA_R5_error_t SARA_R5::getOperator(String *oper) _debugPort->print(F("getOperator: ")); _debugPort->println(*oper); } - //oper->concat('\0'); } } } @@ -1760,7 +1835,7 @@ SARA_R5_error_t SARA_R5::sendSMS(String number, String message) messageCStr[message.length()] = ASCII_CTRL_Z; err = sendCommandWithResponse(messageCStr, SARA_R5_RESPONSE_OK, - NULL, SARA_R5_3_MIN_TIMEOUT, NOT_AT_COMMAND); + NULL, SARA_R5_3_MIN_TIMEOUT, minimumResponseAllocation, NOT_AT_COMMAND); free(messageCStr); } @@ -1786,7 +1861,7 @@ SARA_R5_error_t SARA_R5::getPreferredMessageStorage(int *used, int *total, Strin return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=\"%s\"", SARA_R5_PREF_MESSAGE_STORE, memory.c_str()); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -1803,12 +1878,15 @@ SARA_R5_error_t SARA_R5::getPreferredMessageStorage(int *used, int *total, Strin return err; } - int ret = sscanf(response, "\r\n+CPMS: %d,%d", &u, &t); - if (ret == 2) + int scanned = 0; + char *searchPtr = strstr(response, "+CPMS: "); + if (searchPtr != NULL) + scanned = sscanf(searchPtr, "+CPMS: %d,%d", &u, &t); + if (scanned == 2) { if (_printDebug == true) { - _debugPort->print(F("getPreferredMessageStorage: memory: ")); + _debugPort->print(F("getPreferredMessageStorage: memory1 (read and delete): ")); _debugPort->print(memory); _debugPort->print(F(" used: ")); _debugPort->print(u); @@ -1847,7 +1925,7 @@ SARA_R5_error_t SARA_R5::readSMSmessage(int location, String *unread, String *fr } err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, - SARA_R5_10_SEC_TIMEOUT); + SARA_R5_10_SEC_TIMEOUT, 1024); if (err == SARA_R5_ERROR_SUCCESS) { @@ -1932,6 +2010,25 @@ SARA_R5_error_t SARA_R5::readSMSmessage(int location, String *unread, String *fr return err; } +SARA_R5_error_t SARA_R5::deleteSMSmessage(int location, int deleteFlag) +{ + char *command; + SARA_R5_error_t err; + + command = sara_r5_calloc_char(strlen(SARA_R5_DELETE_MESSAGE) + 12); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + if (deleteFlag == 0) + sprintf(command, "%s=%d", SARA_R5_DELETE_MESSAGE, location); + else + sprintf(command, "%s=%d,%d", SARA_R5_DELETE_MESSAGE, location, deleteFlag); + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, NULL, SARA_R5_55_SECS_TIMEOUT); + + free(command); + return err; +} + SARA_R5_error_t SARA_R5::setBaud(unsigned long baud) { SARA_R5_error_t err; @@ -2021,7 +2118,7 @@ SARA_R5::SARA_R5_gpio_mode_t SARA_R5::getGpioMode(SARA_R5_gpio_t gpio) return GPIO_MODE_INVALID; sprintf(command, "%s?", SARA_R5_COMMAND_GPIO); - response = sara_r5_calloc_char(96); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2063,11 +2160,11 @@ int SARA_R5::socketOpen(SARA_R5_socket_protocol_t protocol, unsigned int localPo if (command == NULL) return -1; if (localPort == 0) - sprintf(command, "%s=%d", SARA_R5_CREATE_SOCKET, protocol); + sprintf(command, "%s=%d", SARA_R5_CREATE_SOCKET, (int)protocol); else - sprintf(command, "%s=%d,%d", SARA_R5_CREATE_SOCKET, protocol, localPort); + sprintf(command, "%s=%d,%d", SARA_R5_CREATE_SOCKET, (int)protocol, localPort); - response = sara_r5_calloc_char(128); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { if (_printDebug == true) @@ -2109,6 +2206,7 @@ int SARA_R5::socketOpen(SARA_R5_socket_protocol_t protocol, unsigned int localPo } sscanf(responseStart, "+USOCR: %d", &sockId); + _lastSocketProtocol[sockId] = (int)protocol; free(command); free(response); @@ -2125,7 +2223,7 @@ SARA_R5_error_t SARA_R5::socketClose(int socket, unsigned long timeout) command = sara_r5_calloc_char(strlen(SARA_R5_CLOSE_SOCKET) + 10); if (command == NULL) return SARA_R5_ERROR_OUT_OF_MEMORY; - response = sara_r5_calloc_char(128); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2165,39 +2263,65 @@ SARA_R5_error_t SARA_R5::socketConnect(int socket, const char *address, return err; } -SARA_R5_error_t SARA_R5::socketWrite(int socket, const char *str) +SARA_R5_error_t SARA_R5::socketConnect(int socket, IPAddress address, + unsigned int port) +{ + char *charAddress = sara_r5_calloc_char(16); + if (charAddress == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + memset(charAddress, 0, 16); + sprintf(charAddress, "%d.%d.%d.%d", address[0], address[1], address[2], address[3]); + + return (socketConnect(socket, (const char *)charAddress, port)); +} + +SARA_R5_error_t SARA_R5::socketWrite(int socket, const char *str, int len) { char *command; char *response; SARA_R5_error_t err; - unsigned long writeDelay; command = sara_r5_calloc_char(strlen(SARA_R5_WRITE_SOCKET) + 16); if (command == NULL) return SARA_R5_ERROR_OUT_OF_MEMORY; - response = sara_r5_calloc_char(128); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); return SARA_R5_ERROR_OUT_OF_MEMORY; } - sprintf(command, "%s=%d,%d", SARA_R5_WRITE_SOCKET, socket, strlen(str)); + int dataLen = len == -1 ? strlen(str) : len; + sprintf(command, "%s=%d,%d", SARA_R5_WRITE_SOCKET, socket, dataLen); err = sendCommandWithResponse(command, "@", response, SARA_R5_2_MIN_TIMEOUT); if (err == SARA_R5_ERROR_SUCCESS) { - writeDelay = millis(); + unsigned long writeDelay = millis(); while (millis() < (writeDelay + 50)) - ; //uBlox specification says to wait 50ms after receiving "@" to write data. + delay(1); //uBlox specification says to wait 50ms after receiving "@" to write data. - if (_printDebug == true) + if (len == -1) + { + if (_printDebug == true) + { + _debugPort->print(F("socketWrite: writing: ")); + _debugPort->println(str); + } + hwPrint(str); + } + else { - _debugPort->print(F("socketWrite: writing: ")); - _debugPort->println(str); + if (_printDebug == true) + { + _debugPort->print(F("socketWrite: writing ")); + _debugPort->print(len); + _debugPort->println(F(" bytes")); + } + hwWriteData(str, len); } - hwPrint(str); + err = waitForResponse(SARA_R5_RESPONSE_OK, SARA_R5_RESPONSE_ERROR, SARA_R5_SOCKET_WRITE_TIMEOUT); } @@ -2220,7 +2344,7 @@ SARA_R5_error_t SARA_R5::socketWrite(int socket, const char *str) SARA_R5_error_t SARA_R5::socketWrite(int socket, String str) { - return socketWrite(socket, str.c_str()); + return socketWrite(socket, str.c_str(), str.length()); } SARA_R5_error_t SARA_R5::socketWriteUDP(int socket, const char *address, int port, const char *str, int len) @@ -2233,7 +2357,7 @@ SARA_R5_error_t SARA_R5::socketWriteUDP(int socket, const char *address, int por command = sara_r5_calloc_char(64); if (command == NULL) return SARA_R5_ERROR_OUT_OF_MEMORY; - response = sara_r5_calloc_char(128); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2269,9 +2393,20 @@ SARA_R5_error_t SARA_R5::socketWriteUDP(int socket, const char *address, int por return err; } -SARA_R5_error_t SARA_R5::socketWriteUDP(int socket, String address, int port, String str, int len) +SARA_R5_error_t SARA_R5::socketWriteUDP(int socket, IPAddress address, int port, const char *str, int len) +{ + char *charAddress = sara_r5_calloc_char(16); + if (charAddress == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + memset(charAddress, 0, 16); + sprintf(charAddress, "%d.%d.%d.%d", address[0], address[1], address[2], address[3]); + + return (socketWriteUDP(socket, (const char *)charAddress, port, str, len)); +} + +SARA_R5_error_t SARA_R5::socketWriteUDP(int socket, String address, int port, String str) { - return socketWriteUDP(socket, address.c_str(), port, str.c_str(), len); + return socketWriteUDP(socket, address.c_str(), port, str.c_str(), str.length()); } SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) @@ -2281,13 +2416,25 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) char *strBegin; int readIndex = 0; SARA_R5_error_t err; + int scanNum = 0; + int readLength = 0; + int socketStore = 0; + + // Check if length is zero + if (length == 0) + { + if (_printDebug == true) + _debugPort->print(F("socketRead: length is 0! Call socketReadAvailable?")); + return SARA_R5_ERROR_UNEXPECTED_PARAM; + } - command = sara_r5_calloc_char(strlen(SARA_R5_READ_SOCKET) + 8); + command = sara_r5_calloc_char(strlen(SARA_R5_READ_SOCKET) + 32); if (command == NULL) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,%d", SARA_R5_READ_SOCKET, socket, length); - response = sara_r5_calloc_char(length + strlen(SARA_R5_READ_SOCKET) + 128); + int responseLength = length + strlen(SARA_R5_READ_SOCKET) + minimumResponseAllocation; + response = sara_r5_calloc_char(responseLength); if (response == NULL) { free(command); @@ -2295,10 +2442,40 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) } err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, - SARA_R5_STANDARD_RESPONSE_TIMEOUT); + SARA_R5_STANDARD_RESPONSE_TIMEOUT, responseLength); if (err == SARA_R5_ERROR_SUCCESS) { + // Extract the data - and check the quote is present + char *searchPtr = strstr(response, "+USORD: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USORD: %d,%d,\"", + &socketStore, &readLength); + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("socketRead: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + + // Check that readLength == length + // TO DO: handle this properly. The user needs to know how many bytes were read. + if (readLength != length) + { + if (_printDebug == true) + { + _debugPort->print(F("socketRead: length mismatch! length=")); + _debugPort->print(length); + _debugPort->print(F(" readLength=")); + _debugPort->println(readLength); + } + } + // Find the first double-quote: strBegin = strchr(response, '\"'); @@ -2309,11 +2486,66 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } - while ((readIndex < length) && (readIndex < (int)strlen(strBegin))) + // Now copy the data into readDest + while (readIndex < readLength) { readDest[readIndex] = strBegin[1 + readIndex]; - readIndex += 1; + readIndex++; + } + + if (_printDebug == true) + _debugPort->println(F("socketRead: success")); + } + + free(command); + free(response); + + return err; +} + +SARA_R5_error_t SARA_R5::socketReadAvailable(int socket, int *length) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum = 0; + int readLength = 0; + int socketStore = 0; + + command = sara_r5_calloc_char(strlen(SARA_R5_READ_SOCKET) + 32); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s=%d,0", SARA_R5_READ_SOCKET, socket); + + response = sara_r5_calloc_char(minimumResponseAllocation); + if (response == NULL) + { + free(command); + return SARA_R5_ERROR_OUT_OF_MEMORY; + } + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, + SARA_R5_STANDARD_RESPONSE_TIMEOUT); + + if (err == SARA_R5_ERROR_SUCCESS) + { + char *searchPtr = strstr(response, "+USORD: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USORD: %d,%d", + &socketStore, &readLength); + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("socketReadAvailable: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } + + *length = readLength; } free(command); @@ -2322,20 +2554,34 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) return err; } -SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest) +SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest, IPAddress *remoteIPAddress, int *remotePort) { char *command; char *response; char *strBegin; int readIndex = 0; SARA_R5_error_t err; + int scanNum = 0; + int remoteIPstore[4] = { 0, 0, 0, 0 }; + int portStore = 0; + int readLength = 0; + int socketStore = 0; + + // Check if length is zero + if (length == 0) + { + if (_printDebug == true) + _debugPort->print(F("socketReadUDP: length is 0! Call socketReadAvailableUDP?")); + return SARA_R5_ERROR_UNEXPECTED_PARAM; + } - command = sara_r5_calloc_char(strlen(SARA_R5_READ_UDP_SOCKET) + 16); + command = sara_r5_calloc_char(strlen(SARA_R5_READ_UDP_SOCKET) + 32); if (command == NULL) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,%d", SARA_R5_READ_UDP_SOCKET, socket, length); - response = sara_r5_calloc_char(length + strlen(SARA_R5_READ_UDP_SOCKET) + 128); + int responseLength = length + strlen(SARA_R5_READ_UDP_SOCKET) + minimumResponseAllocation; + response = sara_r5_calloc_char(responseLength); if (response == NULL) { free(command); @@ -2343,18 +2589,42 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest) } err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, - SARA_R5_STANDARD_RESPONSE_TIMEOUT); + SARA_R5_STANDARD_RESPONSE_TIMEOUT, responseLength); if (err == SARA_R5_ERROR_SUCCESS) { - // Find the third double-quote. This needs to be improved to collect other data. - if (_printDebug == true) - _debugPort->print(F("socketReadUDP: {")); - if (_printDebug == true) - _debugPort->print(response); - if (_printDebug == true) - _debugPort->println(F("}")); + // Extract the data - and check the third quote is present + char *searchPtr = strstr(response, "+USORF: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USORF: %d,\"%d.%d.%d.%d\",%d,%d,\"", + &socketStore, &remoteIPstore[0], &remoteIPstore[1], &remoteIPstore[2], &remoteIPstore[3], + &portStore, &readLength); + if (scanNum != 7) + { + if (_printDebug == true) + { + _debugPort->print(F("socketReadUDP: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + // Check that readLength == length + // TO DO: handle this properly. The user needs to know how many bytes were read. + if (readLength != length) + { + if (_printDebug == true) + { + _debugPort->print(F("socketReadUDP: length mismatch! length=")); + _debugPort->print(length); + _debugPort->print(F(" readLength=")); + _debugPort->println(readLength); + } + } + + // Find the third double-quote strBegin = strchr(response, '\"'); strBegin = strchr(strBegin + 1, '\"'); strBegin = strchr(strBegin + 1, '\"'); @@ -2366,11 +2636,83 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest) return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } - while ((readIndex < length) && (readIndex < (int)strlen(strBegin))) + // Now copy the data into readDest + while (readIndex < readLength) { readDest[readIndex] = strBegin[1 + readIndex]; - readIndex += 1; + readIndex++; + } + + // If remoteIPaddress is not NULL, copy the remote IP address + if (remoteIPAddress != NULL) + { + IPAddress tempAddress; + for (int i = 0; i <= 3; i++) + { + tempAddress[i] = (uint8_t)remoteIPstore[i]; + } + *remoteIPAddress = tempAddress; + } + + // If remotePort is not NULL, copy the remote port + if (remotePort != NULL) + { + *remotePort = portStore; + } + + if (_printDebug == true) + _debugPort->println(F("socketReadUDP: success")); + } + + free(command); + free(response); + + return err; +} + +SARA_R5_error_t SARA_R5::socketReadAvailableUDP(int socket, int *length) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum = 0; + int readLength = 0; + int socketStore = 0; + + command = sara_r5_calloc_char(strlen(SARA_R5_READ_UDP_SOCKET) + 32); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s=%d,0", SARA_R5_READ_UDP_SOCKET, socket); + + response = sara_r5_calloc_char(minimumResponseAllocation); + if (response == NULL) + { + free(command); + return SARA_R5_ERROR_OUT_OF_MEMORY; + } + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, + SARA_R5_STANDARD_RESPONSE_TIMEOUT); + + if (err == SARA_R5_ERROR_SUCCESS) + { + char *searchPtr = strstr(response, "+UPSND: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+UPSND: %d,%d", + &socketStore, &readLength); + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("socketReadAvailableUDP: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } + + *length = readLength; } free(command); @@ -2497,58 +2839,423 @@ SARA_R5_error_t SARA_R5::socketDirectLinkCongestionTimer(int socket, unsigned lo return err; } -//Issues command to get last socket error, then prints to serial. Also updates rx/backlog buffers. -int SARA_R5::socketGetLastError() +SARA_R5_error_t SARA_R5::querySocketType(int socket, SARA_R5_socket_protocol_t *protocol) { - SARA_R5_error_t err; char *command; char *response; - int errorCode = -1; + SARA_R5_error_t err; + int scanNum = 0; + int socketStore = 0; + int paramVal; - command = sara_r5_calloc_char(64); + command = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); if (command == NULL) return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s=%d,0", SARA_R5_SOCKET_CONTROL, socket); - response = sara_r5_calloc_char(128); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); return SARA_R5_ERROR_OUT_OF_MEMORY; } - sprintf(command, "%s", SARA_R5_GET_ERROR); - err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, SARA_R5_STANDARD_RESPONSE_TIMEOUT); if (err == SARA_R5_ERROR_SUCCESS) { - sscanf(response, "+USOER: %d", &errorCode); + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,0,%d", + &socketStore, ¶mVal); + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketType: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + + *protocol = (SARA_R5_socket_protocol_t)paramVal; + _lastSocketProtocol[socketStore] = paramVal; } free(command); free(response); - return errorCode; -} - -IPAddress SARA_R5::lastRemoteIP(void) -{ - return _lastRemoteIP; + return err; } -SARA_R5_error_t SARA_R5::resetHTTPprofile(int profile) +SARA_R5_error_t SARA_R5::querySocketLastError(int socket, int *error) { - SARA_R5_error_t err; char *command; + char *response; + SARA_R5_error_t err; + int scanNum = 0; + int socketStore = 0; + int paramVal; - if (profile >= SARA_R5_NUM_HTTP_PROFILES) - return SARA_R5_ERROR_ERROR; - - command = sara_r5_calloc_char(strlen(SARA_R5_HTTP_PROFILE) + 16); + command = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); if (command == NULL) return SARA_R5_ERROR_OUT_OF_MEMORY; - sprintf(command, "%s=%d", SARA_R5_HTTP_PROFILE, profile); + sprintf(command, "%s=%d,1", SARA_R5_SOCKET_CONTROL, socket); + + response = sara_r5_calloc_char(minimumResponseAllocation); + if (response == NULL) + { + free(command); + return SARA_R5_ERROR_OUT_OF_MEMORY; + } + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, + SARA_R5_STANDARD_RESPONSE_TIMEOUT); + + if (err == SARA_R5_ERROR_SUCCESS) + { + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,1,%d", + &socketStore, ¶mVal); + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketLastError: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + + *error = paramVal; + } + + free(command); + free(response); + + return err; +} + +SARA_R5_error_t SARA_R5::querySocketTotalBytesSent(int socket, uint32_t *total) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum = 0; + int socketStore = 0; + long unsigned int paramVal; + + command = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s=%d,2", SARA_R5_SOCKET_CONTROL, socket); + + response = sara_r5_calloc_char(minimumResponseAllocation); + if (response == NULL) + { + free(command); + return SARA_R5_ERROR_OUT_OF_MEMORY; + } + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, + SARA_R5_STANDARD_RESPONSE_TIMEOUT); + + if (err == SARA_R5_ERROR_SUCCESS) + { + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,2,%lu", + &socketStore, ¶mVal); + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketTotalBytesSent: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + + *total = (uint32_t)paramVal; + } + + free(command); + free(response); + + return err; +} + +SARA_R5_error_t SARA_R5::querySocketTotalBytesReceived(int socket, uint32_t *total) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum = 0; + int socketStore = 0; + long unsigned int paramVal; + + command = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s=%d,3", SARA_R5_SOCKET_CONTROL, socket); + + response = sara_r5_calloc_char(minimumResponseAllocation); + if (response == NULL) + { + free(command); + return SARA_R5_ERROR_OUT_OF_MEMORY; + } + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, + SARA_R5_STANDARD_RESPONSE_TIMEOUT); + + if (err == SARA_R5_ERROR_SUCCESS) + { + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,3,%lu", + &socketStore, ¶mVal); + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketTotalBytesReceived: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + + *total = (uint32_t)paramVal; + } + + free(command); + free(response); + + return err; +} + +SARA_R5_error_t SARA_R5::querySocketRemoteIPAddress(int socket, IPAddress *address, int *port) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum = 0; + int socketStore = 0; + int paramVals[5]; + + command = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s=%d,4", SARA_R5_SOCKET_CONTROL, socket); + + response = sara_r5_calloc_char(minimumResponseAllocation); + if (response == NULL) + { + free(command); + return SARA_R5_ERROR_OUT_OF_MEMORY; + } + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, + SARA_R5_STANDARD_RESPONSE_TIMEOUT); + + if (err == SARA_R5_ERROR_SUCCESS) + { + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,4,\"%d.%d.%d.%d\",%d", + &socketStore, + ¶mVals[0], ¶mVals[1], ¶mVals[2], ¶mVals[3], + ¶mVals[4]); + if (scanNum != 6) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketRemoteIPAddress: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + + IPAddress tempAddress = { (uint8_t)paramVals[0], (uint8_t)paramVals[1], + (uint8_t)paramVals[2], (uint8_t)paramVals[3] }; + *address = tempAddress; + *port = paramVals[4]; + } + + free(command); + free(response); + + return err; +} + +SARA_R5_error_t SARA_R5::querySocketStatusTCP(int socket, SARA_R5_tcp_socket_status_t *status) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum = 0; + int socketStore = 0; + int paramVal; + + command = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s=%d,10", SARA_R5_SOCKET_CONTROL, socket); + + response = sara_r5_calloc_char(minimumResponseAllocation); + if (response == NULL) + { + free(command); + return SARA_R5_ERROR_OUT_OF_MEMORY; + } + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, + SARA_R5_STANDARD_RESPONSE_TIMEOUT); + + if (err == SARA_R5_ERROR_SUCCESS) + { + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,10,%d", + &socketStore, ¶mVal); + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketStatusTCP: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + + *status = (SARA_R5_tcp_socket_status_t)paramVal; + } + + free(command); + free(response); + + return err; +} + +SARA_R5_error_t SARA_R5::querySocketOutUnackData(int socket, uint32_t *total) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum = 0; + int socketStore = 0; + long unsigned int paramVal; + + command = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s=%d,11", SARA_R5_SOCKET_CONTROL, socket); + + response = sara_r5_calloc_char(minimumResponseAllocation); + if (response == NULL) + { + free(command); + return SARA_R5_ERROR_OUT_OF_MEMORY; + } + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, + SARA_R5_STANDARD_RESPONSE_TIMEOUT); + + if (err == SARA_R5_ERROR_SUCCESS) + { + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,11,%lu", + &socketStore, ¶mVal); + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketOutUnackData: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + + *total = (uint32_t)paramVal; + } + + free(command); + free(response); + + return err; +} + +//Issues command to get last socket error, then prints to serial. Also updates rx/backlog buffers. +int SARA_R5::socketGetLastError() +{ + SARA_R5_error_t err; + char *command; + char *response; + int errorCode = -1; + + command = sara_r5_calloc_char(64); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + + response = sara_r5_calloc_char(minimumResponseAllocation); + if (response == NULL) + { + free(command); + return SARA_R5_ERROR_OUT_OF_MEMORY; + } + + sprintf(command, "%s", SARA_R5_GET_ERROR); + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, + SARA_R5_STANDARD_RESPONSE_TIMEOUT); + + if (err == SARA_R5_ERROR_SUCCESS) + { + char *searchPtr = strstr(response, "+USOER: "); + if (searchPtr != NULL) + sscanf(searchPtr, "+USOER: %d", &errorCode); + } + + free(command); + free(response); + + return errorCode; +} + +IPAddress SARA_R5::lastRemoteIP(void) +{ + return _lastRemoteIP; +} + +SARA_R5_error_t SARA_R5::resetHTTPprofile(int profile) +{ + SARA_R5_error_t err; + char *command; + + if (profile >= SARA_R5_NUM_HTTP_PROFILES) + return SARA_R5_ERROR_ERROR; + + command = sara_r5_calloc_char(strlen(SARA_R5_HTTP_PROFILE) + 16); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s=%d", SARA_R5_HTTP_PROFILE, profile); err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, NULL, SARA_R5_STANDARD_RESPONSE_TIMEOUT); @@ -2782,7 +3489,7 @@ SARA_R5_error_t SARA_R5::getHTTPprotocolError(int profile, int *error_class, int return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d", SARA_R5_HTTP_PROTOCOL_ERROR, profile); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2794,8 +3501,12 @@ SARA_R5_error_t SARA_R5::getHTTPprotocolError(int profile, int *error_class, int if (err == SARA_R5_ERROR_SUCCESS) { - if (sscanf(response, "\r\n+UHTTPER: %d,%d,%d\r\n", - &rprofile, &eclass, &ecode) == 3) + int scanned = 0; + char *searchPtr = strstr(response, "+UHTTPER: "); + if (searchPtr != NULL) + scanned = sscanf(searchPtr, "+UHTTPER: %d,%d,%d\r\n", + &rprofile, &eclass, &ecode); + if (scanned == 3) { *error_class = eclass; *error_code = ecode; @@ -2920,6 +3631,61 @@ SARA_R5_error_t SARA_R5::activatePDPcontext(bool status, int cid) return err; } +SARA_R5_error_t SARA_R5::getNetworkAssignedIPAddress(int profile, IPAddress *address) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum = 0; + int profileStore = 0; + int paramTag = 0; // 0: IP address: dynamic IP address assigned during PDP context activation + int paramVals[4]; + + command = sara_r5_calloc_char(strlen(SARA_R5_NETWORK_ASSIGNED_DATA) + 16); + if (command == NULL) + return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s=%d,%d", SARA_R5_NETWORK_ASSIGNED_DATA, profile, paramTag); + + response = sara_r5_calloc_char(minimumResponseAllocation); + if (response == NULL) + { + free(command); + return SARA_R5_ERROR_OUT_OF_MEMORY; + } + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, response, + SARA_R5_STANDARD_RESPONSE_TIMEOUT); + + if (err == SARA_R5_ERROR_SUCCESS) + { + char *searchPtr = strstr(response, "+UPSND: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+UPSND: %d,%d,\"%d.%d.%d.%d\"", + &profileStore, ¶mTag, + ¶mVals[0], ¶mVals[1], ¶mVals[2], ¶mVals[3]); + if (scanNum != 6) + { + if (_printDebug == true) + { + _debugPort->print(F("getNetworkAssignedIPAddress: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + + IPAddress tempAddress = { (uint8_t)paramVals[0], (uint8_t)paramVals[1], + (uint8_t)paramVals[2], (uint8_t)paramVals[3] }; + *address = tempAddress; + } + + free(command); + free(response); + + return err; +} + bool SARA_R5::isGPSon(void) { SARA_R5_error_t err; @@ -2932,7 +3698,7 @@ bool SARA_R5::isGPSon(void) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s?", SARA_R5_GNSS_POWER); - response = sara_r5_calloc_char(24); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -3092,7 +3858,7 @@ SARA_R5_error_t SARA_R5::gpsGetRmc(struct PositionData *pos, struct SpeedData *s return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s?", SARA_R5_GNSS_GPRMC); - response = sara_r5_calloc_char(96); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -3212,7 +3978,7 @@ SARA_R5_error_t SARA_R5::getFileContents(String filename, String *contents) } err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, - response, SARA_R5_STANDARD_RESPONSE_TIMEOUT); + response, SARA_R5_STANDARD_RESPONSE_TIMEOUT, 1072); if (err != SARA_R5_ERROR_SUCCESS) { free(command); @@ -3526,7 +4292,7 @@ SARA_R5_error_t SARA_R5::getMNOprofile(mobile_network_operator_t *mno) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s?", SARA_R5_COMMAND_MNO); - response = sara_r5_calloc_char(48); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -3542,10 +4308,13 @@ SARA_R5_error_t SARA_R5::getMNOprofile(mobile_network_operator_t *mno) return err; } - int ret = sscanf(response, "\r\n+UMNOPROF: %d,%d,%d,%d", &oStore, &d, &r, &u); + int scanned = 0; + char *searchPtr = strstr(response, "+UMNOPROF: "); + if (searchPtr != NULL) + scanned = sscanf(searchPtr, "+UMNOPROF: %d,%d,%d,%d", &oStore, &d, &r, &u); o = (mobile_network_operator_t)oStore; - if (ret >= 1) + if (scanned >= 1) { if (_printDebug == true) { @@ -3570,8 +4339,7 @@ SARA_R5_error_t SARA_R5::waitForResponse(const char *expectedResponse, const cha unsigned long timeIn; bool found = false; int responseIndex = 0, errorIndex = 0; - int backlogIndex = strlen(saraResponseBacklog); - //bool printedSomething = false; + // bool printedSomething = false; timeIn = millis(); @@ -3609,11 +4377,19 @@ SARA_R5_error_t SARA_R5::waitForResponse(const char *expectedResponse, const cha { errorIndex = 0; } - //This is a global array that holds the backlog of any events + //_saraResponseBacklog is a global array that holds the backlog of any events //that came in while waiting for response. To be processed later within bufferedPoll(). - //Note: the expectedResponse or expectedError will also be added to the backlog - if (backlogIndex < RXBuffSize) // Don't overflow the buffer - saraResponseBacklog[backlogIndex++] = c; + //Note: the expectedResponse or expectedError will also be added to the backlog. + //The backlog is only used by bufferedPoll to process the URCs - which are all readable. + //bufferedPoll uses strtok - which does not like NULL characters. + //So let's make sure no NULLs end up in the backlog! + if (_saraResponseBacklogLength < _RXBuffSize) // Don't overflow the buffer + { + if (c == '\0') + _saraResponseBacklog[_saraResponseBacklogLength++] = '0'; // Change NULLs to ASCII Zeros + else + _saraResponseBacklog[_saraResponseBacklogLength++] = c; + } } } @@ -3640,7 +4416,7 @@ SARA_R5_error_t SARA_R5::waitForResponse(const char *expectedResponse, const cha SARA_R5_error_t SARA_R5::sendCommandWithResponse( const char *command, const char *expectedResponse, char *responseDest, - unsigned long commandTimeout, bool at) + unsigned long commandTimeout, int destSize, bool at) { bool found = false; int index = 0; @@ -3653,7 +4429,7 @@ SARA_R5_error_t SARA_R5::sendCommandWithResponse( if (_printDebug == true) _debugPort->println(String(command)); - int backlogIndex = sendCommand(command, at); //Sending command needs to dump data to backlog buffer as well. + sendCommand(command, at); //Sending command needs to dump data to backlog buffer as well. unsigned long timeIn = millis(); while ((!found) && ((timeIn + commandTimeout) > millis())) @@ -3670,7 +4446,20 @@ SARA_R5_error_t SARA_R5::sendCommandWithResponse( } if (responseDest != NULL) { - responseDest[destIndex++] = c; + if (destIndex < destSize) // Only add this char to response if there is room for it + responseDest[destIndex] = c; + destIndex++; + if (destIndex == destSize) + { + if (_printDebug == true) + { + if (printedSomething) + _debugPort->println(); + _debugPort->print(F("sendCommandWithResponse: Panic! responseDest is full!")); + if (printedSomething) + _debugPort->print(F("sendCommandWithResponse: Ignored response: ")); + } + } } charsRead++; if (c == expectedResponse[index]) @@ -3684,11 +4473,19 @@ SARA_R5_error_t SARA_R5::sendCommandWithResponse( { index = 0; } - //This is a global array that holds the backlog of any events + //_saraResponseBacklog is a global array that holds the backlog of any events //that came in while waiting for response. To be processed later within bufferedPoll(). //Note: the expectedResponse or expectedError will also be added to the backlog - if (backlogIndex < RXBuffSize) // Don't overflow the buffer - saraResponseBacklog[backlogIndex++] = c; + //The backlog is only used by bufferedPoll to process the URCs - which are all readable. + //bufferedPoll uses strtok - which does not like NULL characters. + //So let's make sure no NULLs end up in the backlog! + if (_saraResponseBacklogLength < _RXBuffSize) // Don't overflow the buffer + { + if (c == '\0') + _saraResponseBacklog[_saraResponseBacklogLength++] = '0'; // Change NULLs to ASCII Zeros + else + _saraResponseBacklog[_saraResponseBacklogLength++] = c; + } } } @@ -3716,24 +4513,31 @@ SARA_R5_error_t SARA_R5::sendCommandWithResponse( SARA_R5_error_t SARA_R5::sendCustomCommandWithResponse(const char *command, const char *expectedResponse, char *responseDest, unsigned long commandTimeout, bool at) { - return sendCommandWithResponse(command, expectedResponse, responseDest, commandTimeout, at); + // Assume the user has allocated enough storage for any response. Set destSize to 32766. + return sendCommandWithResponse(command, expectedResponse, responseDest, commandTimeout, 32766, at); } -int SARA_R5::sendCommand(const char *command, bool at) +void SARA_R5::sendCommand(const char *command, bool at) { - int backlogIndex = strlen(saraResponseBacklog); - - //Spend up to rxWindowUS microseconds copying any incoming serial data into the backlog - unsigned long timeIn = micros(); + //Spend up to _rxWindowMillis milliseconds copying any incoming serial data into the backlog + unsigned long timeIn = millis(); if (hwAvailable() > 0) //hwAvailable can return -1 if the serial port is NULL { - while (((micros() - timeIn) < rxWindowUS) && (backlogIndex < RXBuffSize)) //May need to escape on newline? + while (((millis() - timeIn) < _rxWindowMillis) && (_saraResponseBacklogLength < _RXBuffSize)) //May need to escape on newline? { if (hwAvailable() > 0) //hwAvailable can return -1 if the serial port is NULL { + //_saraResponseBacklog is a global array that holds the backlog of any events + //that came in while waiting for response. To be processed later within bufferedPoll(). + //Note: the expectedResponse or expectedError will also be added to the backlog + //The backlog is only used by bufferedPoll to process the URCs - which are all readable. + //bufferedPoll uses strtok - which does not like NULL characters. + //So let's make sure no NULLs end up in the backlog! char c = readChar(); - saraResponseBacklog[backlogIndex++] = c; - timeIn = micros(); + if (c == '\0') // Make sure no NULL characters end up in the backlog! Change them to ASCII Zeros + c = '0'; + _saraResponseBacklog[_saraResponseBacklogLength++] = c; + timeIn = millis(); } } } @@ -3749,8 +4553,6 @@ int SARA_R5::sendCommand(const char *command, bool at) { hwPrint(command); } - - return backlogIndex; // Return the new backlog length } SARA_R5_error_t SARA_R5::parseSocketReadIndication(int socket, int length) @@ -3763,6 +4565,10 @@ SARA_R5_error_t SARA_R5::parseSocketReadIndication(int socket, int length) return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } + // Return now if both callbacks pointers are NULL - otherwise the data will be read and lost! + if ((_socketReadCallback == NULL) && (_socketReadCallbackPlus == NULL)) + return SARA_R5_ERROR_INVALID; + readDest = sara_r5_calloc_char(length + 1); if (readDest == NULL) return SARA_R5_ERROR_OUT_OF_MEMORY; @@ -3776,7 +4582,17 @@ SARA_R5_error_t SARA_R5::parseSocketReadIndication(int socket, int length) if (_socketReadCallback != NULL) { - _socketReadCallback(socket, String(readDest)); + String dataAsString = ""; // Create an empty string + for (int i = 0; i < length; i++) // Copy the data from readDest into the String in a binary-compatible way + dataAsString.concat(readDest[i]); + _socketReadCallback(socket, dataAsString); + } + + if (_socketReadCallbackPlus != NULL) + { + IPAddress dummyAddress = { 0, 0, 0, 0 }; + int dummyPort = 0; + _socketReadCallbackPlus(socket, (const char *)readDest, length, dummyAddress, dummyPort); } free(readDest); @@ -3787,19 +4603,25 @@ SARA_R5_error_t SARA_R5::parseSocketReadIndicationUDP(int socket, int length) { SARA_R5_error_t err; char *readDest; + IPAddress remoteAddress = { 0, 0, 0, 0 }; + int remotePort = 0; if ((socket < 0) || (length < 0)) { return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } + // Return now if both callbacks pointers are NULL - otherwise the data will be read and lost! + if ((_socketReadCallback == NULL) && (_socketReadCallbackPlus == NULL)) + return SARA_R5_ERROR_INVALID; + readDest = sara_r5_calloc_char(length + 1); if (readDest == NULL) { return SARA_R5_ERROR_OUT_OF_MEMORY; } - err = socketReadUDP(socket, length, readDest); + err = socketReadUDP(socket, length, readDest, &remoteAddress, &remotePort); if (err != SARA_R5_ERROR_SUCCESS) { free(readDest); @@ -3808,7 +4630,15 @@ SARA_R5_error_t SARA_R5::parseSocketReadIndicationUDP(int socket, int length) if (_socketReadCallback != NULL) { - _socketReadCallback(socket, String(readDest)); + String dataAsString = ""; // Create an empty string + for (int i = 0; i < length; i++) // Copy the data from readDest into the String in a binary-compatible way + dataAsString.concat(readDest[i]); + _socketReadCallback(socket, dataAsString); + } + + if (_socketReadCallbackPlus != NULL) + { + _socketReadCallbackPlus(socket, (const char *)readDest, length, remoteAddress, remotePort); } free(readDest); @@ -4044,27 +4874,29 @@ char *SARA_R5::sara_r5_calloc_char(size_t num) void SARA_R5::pruneBacklog() { char *event; - char pruneBuffer[RXBuffSize]; - memset(pruneBuffer, 0, RXBuffSize); // Create a buffer to store the stuff we don't want to prune - // if (strlen(saraResponseBacklog) > 0) //Handy for debugging new parsing. - // { - // if (_printDebug == true) - // _debugPort->println(F("pruneBacklog: pruned backlog was:")); - // if (_printDebug == true) - // _debugPort->println(saraResponseBacklog); - // if (_printDebug == true) - // _debugPort->println(F("pruneBacklog: end of pruned backlog")); - // } - // else + // if (_printDebug == true) // { - // if (_printDebug == true) + // if (_saraResponseBacklogLength > 0) //Handy for debugging new parsing. + // { + // _debugPort->println(F("pruneBacklog: before pruning, backlog was:")); + // _debugPort->println(_saraResponseBacklog); + // _debugPort->println(F("pruneBacklog: end of backlog")); + // } + // else + // { // _debugPort->println(F("pruneBacklog: backlog was empty")); + // } // } - event = strtok(saraResponseBacklog, "\r\n"); // Look for an 'event' - something ending in \r\n + memset(_pruneBuffer, 0, _RXBuffSize); // Clear the _pruneBuffer - while (event != NULL) //If event actionable, add to pruneBuffer. + _saraResponseBacklogLength = 0; // Zero the backlog length + + char *preservedEvent; + event = strtok_r(_saraResponseBacklog, "\r\n", &preservedEvent); // Look for an 'event' - something ending in \r\n + + while (event != NULL) //If event is actionable, add it to pruneBuffer. { // These are the events we want to keep so they can be processed by poll / bufferedPoll if ((strstr(event, "+UUSORD:") != NULL) @@ -4077,29 +4909,29 @@ void SARA_R5::pruneBacklog() || (strstr(event, "+UUPING:") != NULL) || (strstr(event, "+UUHTTPCR:") != NULL)) { - strcat(pruneBuffer, event); - strcat(pruneBuffer, "\r\n"); //strtok blows away delimiter, but we want that for later. + strcat(_pruneBuffer, event); // The URCs are all readable text so using strcat is OK + strcat(_pruneBuffer, "\r\n"); // strtok blows away delimiter, but we want that for later. + _saraResponseBacklogLength += strlen(event) + 2; // Add the length of this event to _saraResponseBacklogLength } - event = strtok(NULL, "\r\n"); // Walk though any remaining events + event = strtok_r(NULL, "\r\n", &preservedEvent); // Walk though any remaining events } - memset(saraResponseBacklog, 0, RXBuffSize); //Clear out backlog buffer. - strcpy(saraResponseBacklog, pruneBuffer); //Copy the stuff we didn't prune into the backlog + memset(_saraResponseBacklog, 0, _RXBuffSize); //Clear out backlog buffer. + memcpy(_saraResponseBacklog, _pruneBuffer, _saraResponseBacklogLength); //Copy the pruned buffer back into the backlog - // if (strlen(saraResponseBacklog) > 0) //Handy for debugging new parsing. - // { - // if (_printDebug == true) - // _debugPort->println(F("pruneBacklog: pruned backlog is now:")); - // if (_printDebug == true) - // _debugPort->println(saraResponseBacklog); - // if (_printDebug == true) - // _debugPort->println(F("pruneBacklog: end of pruned backlog")); - // } - // else + // if (_printDebug == true) // { - // if (_printDebug == true) + // if (_saraResponseBacklogLength > 0) //Handy for debugging new parsing. + // { + // _debugPort->println(F("pruneBacklog: after pruning, backlog is now:")); + // _debugPort->println(_saraResponseBacklog); + // _debugPort->println(F("pruneBacklog: end of backlog")); + // } + // else + // { // _debugPort->println(F("pruneBacklog: backlog is now empty")); + // } // } free(event); diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index 7ed099d..e9e8776 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -78,6 +78,7 @@ // Timing #define SARA_R5_STANDARD_RESPONSE_TIMEOUT 1000 #define SARA_R5_10_SEC_TIMEOUT 10000 +#define SARA_R5_55_SECS_TIMEOUT 55000 #define SARA_R5_2_MIN_TIMEOUT 120000 #define SARA_R5_3_MIN_TIMEOUT 180000 #define SARA_R5_SET_BAUD_TIMEOUT 500 @@ -122,6 +123,7 @@ const char SARA_R5_SEND_TEXT[] = "+CMGS"; // Send SMS message const char SARA_R5_NEW_MESSAGE_IND[] = "+CNMI"; // New [SMS] message indication const char SARA_R5_PREF_MESSAGE_STORE[] = "+CPMS"; // Preferred message storage const char SARA_R5_READ_TEXT_MESSAGE[] = "+CMGR"; // Read message +const char SARA_R5_DELETE_MESSAGE[] = "+CMGD"; // Delete message // V24 control and V25ter (UART interface) const char SARA_R5_FLOW_CONTROL[] = "&K"; // Flow control const char SARA_R5_COMMAND_BAUD[] = "+IPR"; // Baud rate @@ -131,6 +133,7 @@ const char SARA_R5_MESSAGE_PDP_CONFIG[] = "+UPSD"; // Packet switched const char SARA_R5_MESSAGE_PDP_ACTION[] = "+UPSDA"; // Perform the action for the specified PSD profile const char SARA_R5_MESSAGE_PDP_CONTEXT_ACTIVATE[] = "+CGACT"; // Activates or deactivates the specified PDP context const char SARA_R5_MESSAGE_ENTER_PPP[] = "D"; +const char SARA_R5_NETWORK_ASSIGNED_DATA[] = "+UPSND"; // Packet switched network-assigned data // ### GPIO const char SARA_R5_COMMAND_GPIO[] = "+UGPIOC"; // GPIO Configuration // ### IP @@ -144,6 +147,7 @@ const char SARA_R5_READ_UDP_SOCKET[] = "+USORF"; // Read UDP data from a sock const char SARA_R5_LISTEN_SOCKET[] = "+USOLI"; // Listen for connection on socket const char SARA_R5_GET_ERROR[] = "+USOER"; // Get last socket error. const char SARA_R5_SOCKET_DIRECT_LINK[] = "+USODL"; // Set socket in Direct Link mode +const char SARA_R5_SOCKET_CONTROL[] = "+USOCTL"; // Query the socket parameters const char SARA_R5_UD_CONFIGURATION[] = "+UDCONF"; // User Datagram Configuration // ### Ping const char SARA_R5_PING_COMMAND[] = "+UPING"; // Ping @@ -179,6 +183,10 @@ const char ASCII_ESC = 0x1B; #define NOT_AT_COMMAND false #define AT_COMMAND true +// The minimum memory allocation for responses from sendCommandWithResponse +// This needs to be large enough to hold the response you're expecting plus and URC's that may arrive during the timeout +#define minimumResponseAllocation 128 + #define SARA_R5_NUM_SOCKETS 6 #define NUM_SUPPORTED_BAUD 6 @@ -312,6 +320,21 @@ typedef enum SARA_R5_UDP = 17 } SARA_R5_socket_protocol_t; +typedef enum +{ + SARA_R5_TCP_SOCKET_STATUS_INACTIVE, + SARA_R5_TCP_SOCKET_STATUS_LISTEN, + SARA_R5_TCP_SOCKET_STATUS_SYN_SENT, + SARA_R5_TCP_SOCKET_STATUS_SYN_RCVD, + SARA_R5_TCP_SOCKET_STATUS_ESTABLISHED, + SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_1, + SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_2, + SARA_R5_TCP_SOCKET_STATUS_CLOSE_WAIT, + SARA_R5_TCP_SOCKET_STATUS_CLOSING, + SARA_R5_TCP_SOCKET_STATUS_LAST_ACK, + SARA_R5_TCP_SOCKET_STATUS_TIME_WAIT +} SARA_R5_tcp_socket_status_t; + typedef enum { SARA_R5_MESSAGE_FORMAT_PDU = 0, @@ -357,9 +380,6 @@ typedef enum //SARA_R5_SIM_CSD_CALL_ACTIVE // Not reported by SARA-R5 } SARA_R5_sim_states_t; -const int RXBuffSize = 2056; -const int rxWindowUS = 1000; - #define SARA_R5_NUM_PSD_PROFILES 6 // Number of supported PSD profiles #define SARA_R5_NUM_PDP_CONTEXT_IDENTIFIERS 11 // Number of supported PDP context identifiers #define SARA_R5_NUM_HTTP_PROFILES 4 // Number of supported HTTP profiles @@ -449,13 +469,12 @@ typedef enum class SARA_R5 : public Print { public: - char saraRXBuffer[RXBuffSize]; - char saraResponseBacklog[RXBuffSize]; - - // Constructor + // Constructor + // The library will use the powerPin and resetPin (if provided) to power the module off/on and perform an emergency reset + // maxInitDepth sets the maximum number of initialization attempts (recursive). .init is called by .begin. SARA_R5(int powerPin = SARA_R5_POWER_PIN, int resetPin = SARA_R5_RESET_PIN, uint8_t maxInitDepth = 9); - // Begin -- initialize BT module and ensure it's connected + // Begin -- initialize module and ensure it's connected #ifdef SARA_R5_SOFTWARE_SERIAL_ENABLED bool begin(SoftwareSerial &softSerial, unsigned long baud = 9600); #endif @@ -472,23 +491,29 @@ class SARA_R5 : public Print SARA_R5_error_t modulePowerOff(void); // Graceful disconnect and shutdown using +CPWROFF. void modulePowerOn(void); // Requires access to the PWR_ON pin - // Loop polling and polling setup + // Loop polling and polling setup - process URC's etc. from the module // This function was originally written by Matthew Menze for the LTE Shield (SARA-R4) library // See: https://github.com/sparkfun/SparkFun_LTE_Shield_Arduino_Library/pull/8 // It does the same job as ::poll but also processed any 'old' data stored in the backlog first // It also has a built-in timeout - which ::poll does not + // Use this - it is way better than ::poll. Thank you Natthew! bool bufferedPoll(void); // This is the original poll function. // It is 'blocking' - it does not return when serial data is available until it receives a `\n`. // ::bufferedPoll is the new improved version. It processes any data in the backlog and includes a timeout. + // Retained for backward-compatibility and just in case you do want to (temporarily) ignore any data in the backlog bool poll(void); // Callbacks (called during polling) - void setSocketListenCallback(void (*socketListenCallback)(int, IPAddress, unsigned int, int, IPAddress, unsigned int)); - void setSocketReadCallback(void (*socketReadCallback)(int, String)); - void setSocketCloseCallback(void (*socketCloseCallback)(int)); + void setSocketListenCallback(void (*socketListenCallback)(int, IPAddress, unsigned int, int, IPAddress, unsigned int)); // listen Socket, local IP Address, listen Port, socket, remote IP Address, port + // This is the original read socket callback - called when a +UUSORD or +UUSORF URC is received + // It works - and handles binary data correctly - but the remote IP Address and Port are lost for UDP connections + // setSocketReadCallbackPlus is preferred! + void setSocketReadCallback(void (*socketReadCallback)(int, String)); // socket, read data + void setSocketReadCallbackPlus(void (*socketReadCallbackPlus)(int, const char *, int, IPAddress, int)); // socket, read data, length, remoteAddress, remotePort + void setSocketCloseCallback(void (*socketCloseCallback)(int)); // socket void setGpsReadCallback(void (*gpsRequestCallback)(ClockData time, PositionData gps, SpeedData spd, unsigned long uncertainty)); void setSIMstateReportCallback(void (*simStateRequestCallback)(SARA_R5_sim_states_t state)); @@ -519,17 +544,17 @@ class SARA_R5 : public Print String clock(void); // TODO: Return a clock struct SARA_R5_error_t clock(uint8_t *y, uint8_t *mo, uint8_t *d, - uint8_t *h, uint8_t *min, uint8_t *s, int8_t *tz); // TZ can be +/- - SARA_R5_error_t autoTimeZone(bool enable); - SARA_R5_error_t setUtimeMode(SARA_R5_utime_mode_t mode = SARA_R5_UTIME_MODE_PPS, SARA_R5_utime_sensor_t sensor = SARA_R5_UTIME_SENSOR_GNSS_LTE); + uint8_t *h, uint8_t *min, uint8_t *s, int8_t *tz); // TZ can be +/- and is in increments of 15 minutes. -28 == 7 hours behind UTC/GMT + SARA_R5_error_t autoTimeZone(bool enable); // Enable/disable automatic time zone adjustment + SARA_R5_error_t setUtimeMode(SARA_R5_utime_mode_t mode = SARA_R5_UTIME_MODE_PPS, SARA_R5_utime_sensor_t sensor = SARA_R5_UTIME_SENSOR_GNSS_LTE); // Time mode, source etc. (+UTIME) SARA_R5_error_t getUtimeMode(SARA_R5_utime_mode_t *mode, SARA_R5_utime_sensor_t *sensor); - SARA_R5_error_t setUtimeIndication(SARA_R5_utime_urc_configuration_t config = SARA_R5_UTIME_URC_CONFIGURATION_ENABLED); + SARA_R5_error_t setUtimeIndication(SARA_R5_utime_urc_configuration_t config = SARA_R5_UTIME_URC_CONFIGURATION_ENABLED); // +UTIMEIND SARA_R5_error_t getUtimeIndication(SARA_R5_utime_urc_configuration_t *config); - SARA_R5_error_t setUtimeConfiguration(int32_t offsetNanoseconds = 0, int32_t offsetSeconds = 0); + SARA_R5_error_t setUtimeConfiguration(int32_t offsetNanoseconds = 0, int32_t offsetSeconds = 0); // +UTIMECFG SARA_R5_error_t getUtimeConfiguration(int32_t *offsetNanoseconds, int32_t *offsetSeconds); // Network service AT commands - int8_t rssi(void); + int8_t rssi(void); // Receive signal strength SARA_R5_registration_status_t registration(void); bool setNetworkProfile(mobile_network_operator_t mno, bool autoReset = false, bool urcNotification = false); mobile_network_operator_t getNetworkProfile(void); @@ -576,6 +601,11 @@ class SARA_R5 : public Print SARA_R5_error_t sendSMS(String number, String message); SARA_R5_error_t getPreferredMessageStorage(int *used, int *total, String memory = "ME"); SARA_R5_error_t readSMSmessage(int location, String *unread, String *from, String *dateTime, String *message); + SARA_R5_error_t deleteSMSmessage(int location, int deleteFlag = 0); // Default to deleting the single message at the specified location + SARA_R5_error_t deleteReadSMSmessages(void) { return (deleteSMSmessage( 1, 1 )); }; // Delete all the read messages from preferred storage + SARA_R5_error_t deleteReadSentSMSmessages(void) { return (deleteSMSmessage( 1, 2 )); }; // Delete the read and sent messages from preferred storage + SARA_R5_error_t deleteReadSentUnsentSMSmessages(void) { return (deleteSMSmessage( 1, 3 )); }; // Delete the read, sent and unsent messages from preferred storage + SARA_R5_error_t deleteAllSMSmessages(void) { return (deleteSMSmessage( 1, 4 )); }; // Delete the read, sent, unsent and unread messages from preferred storage // V24 Control and V25ter (UART interface) AT commands SARA_R5_error_t setBaud(unsigned long baud); @@ -633,22 +663,54 @@ class SARA_R5 : public Print SARA_R5_gpio_mode_t getGpioMode(SARA_R5_gpio_t gpio); // IP Transport Layer - int socketOpen(SARA_R5_socket_protocol_t protocol, unsigned int localPort = 0); - SARA_R5_error_t socketClose(int socket, unsigned long timeout = SARA_R5_2_MIN_TIMEOUT); - SARA_R5_error_t socketConnect(int socket, const char *address, unsigned int port); - SARA_R5_error_t socketWrite(int socket, const char *str); - SARA_R5_error_t socketWrite(int socket, String str); + int socketOpen(SARA_R5_socket_protocol_t protocol, unsigned int localPort = 0); // Open a socket. Returns the socket number. Not required for UDP sockets. + SARA_R5_error_t socketClose(int socket, unsigned long timeout = SARA_R5_2_MIN_TIMEOUT); // Close the socket + SARA_R5_error_t socketConnect(int socket, const char *address, unsigned int port); // TCP - connect to a remote IP Address using the specified port + SARA_R5_error_t socketConnect(int socket, IPAddress address, unsigned int port); + // Write data to the specified socket. Works with binary data - but you must specify the data length when using the const char * version + // Works with both TCP and UDP sockets - but socketWriteUDP is preferred for UDP and doesn't require socketOpen to be called first + SARA_R5_error_t socketWrite(int socket, const char *str, int len = -1); + SARA_R5_error_t socketWrite(int socket, String str); // OK for binary data + // Write UDP data to the specified IP Address and port. + // Works with binary data - but you must specify the data length when using the const char * versions + // If you let len default to -1, strlen is used to calculate the data length - and will be incorrect for binary data SARA_R5_error_t socketWriteUDP(int socket, const char *address, int port, const char *str, int len = -1); - SARA_R5_error_t socketWriteUDP(int socket, String address, int port, String str, int len = -1); + SARA_R5_error_t socketWriteUDP(int socket, IPAddress address, int port, const char *str, int len = -1); + SARA_R5_error_t socketWriteUDP(int socket, String address, int port, String str); + // Read data from the specified socket + // Call socketReadAvailable first to determine how much data is available - or use the callbacks (triggered by URC's) + // Works for both TCP and UDP - but socketReadUDP is preferred for UDP as it records the remote IP Address and port SARA_R5_error_t socketRead(int socket, int length, char *readDest); - SARA_R5_error_t socketReadUDP(int socket, int length, char *readDest); + // Return the number of bytes available (waiting to be read) on the chosen socket + // Uses +USORD. Valid for both TCP and UDP sockets - but socketReadAvailableUDP is preferred for UDP + SARA_R5_error_t socketReadAvailable(int socket, int *length); + // Read data from the specified UDP port + // Call socketReadAvailableUDP first to determine how much data is available - or use the callbacks (triggered by URC's) + // The remote IP Address and port are returned via *remoteIPAddress and *remotePort (if not NULL) + SARA_R5_error_t socketReadUDP(int socket, int length, char *readDest, IPAddress *remoteIPAddress = NULL, int *remotePort = NULL); + // Return the number of bytes available (waiting to be read) on the chosen UDP socket + SARA_R5_error_t socketReadAvailableUDP(int socket, int *length); + // Start listening for a connection on the specified port. The connection is reported via the socket listen callback SARA_R5_error_t socketListen(int socket, unsigned int port); + // Place the socket into direct link mode - making it easy to transfer binary data. Wait two seconds and then send +++ to exit the link. SARA_R5_error_t socketDirectLinkMode(int socket); + // Configure when direct link data is sent SARA_R5_error_t socketDirectLinkTimeTrigger(int socket, unsigned long timerTrigger); SARA_R5_error_t socketDirectLinkDataLengthTrigger(int socket, int dataLengthTrigger); SARA_R5_error_t socketDirectLinkCharacterTrigger(int socket, int characterTrigger); SARA_R5_error_t socketDirectLinkCongestionTimer(int socket, unsigned long congestionTimer); + // Use +USOCTL (Socket control) to query the socket parameters + SARA_R5_error_t querySocketType(int socket, SARA_R5_socket_protocol_t *protocol); + SARA_R5_error_t querySocketLastError(int socket, int *error); + SARA_R5_error_t querySocketTotalBytesSent(int socket, uint32_t *total); + SARA_R5_error_t querySocketTotalBytesReceived(int socket, uint32_t *total); + SARA_R5_error_t querySocketRemoteIPAddress(int socket, IPAddress *address, int *port); + SARA_R5_error_t querySocketStatusTCP(int socket, SARA_R5_tcp_socket_status_t *status); + SARA_R5_error_t querySocketOutUnackData(int socket, uint32_t *total); + // Return the most recent socket error int socketGetLastError(); + // Return the remote IP Address from the most recent socket listen indication (socket connection) + // Use the socket listen callback to get the full address and port information IPAddress lastRemoteIP(void); // Ping @@ -669,12 +731,14 @@ class SARA_R5 : public Print SARA_R5_error_t sendHTTPPOSTdata(int profile, String path, String responseFilename, String data, SARA_R5_http_content_types_t httpContentType); // Packet Switched Data + // Configure the PDP using +UPSD. See SARA_R5_pdp_configuration_parameter_t for the list of parameters: protocol, APN, username, DNS, etc. SARA_R5_error_t setPDPconfiguration(int profile, SARA_R5_pdp_configuration_parameter_t parameter, int value); // Set parameters in the chosen PSD profile SARA_R5_error_t setPDPconfiguration(int profile, SARA_R5_pdp_configuration_parameter_t parameter, SARA_R5_pdp_protocol_type_t value); // Set parameters in the chosen PSD profile SARA_R5_error_t setPDPconfiguration(int profile, SARA_R5_pdp_configuration_parameter_t parameter, String value); // Set parameters in the chosen PSD profile SARA_R5_error_t setPDPconfiguration(int profile, SARA_R5_pdp_configuration_parameter_t parameter, IPAddress value); // Set parameters in the chosen PSD profile - SARA_R5_error_t performPDPaction(int profile, SARA_R5_pdp_actions_t action); // Performs the requested action for the specified PSD profile. - SARA_R5_error_t activatePDPcontext(bool status, int cid = -1); // Activates or deactivates the specified PDP context. Default to all (cid = -1) + SARA_R5_error_t performPDPaction(int profile, SARA_R5_pdp_actions_t action); // Performs the requested action for the specified PSD profile: reset, store, load, activate, deactivate + SARA_R5_error_t activatePDPcontext(bool status, int cid = -1); // Activates or deactivates the specified PDP context. Default to all (cid = -1) + SARA_R5_error_t getNetworkAssignedIPAddress(int profile, IPAddress *address); // Get the dynamic IP address assigned during PDP context activation // GPS typedef enum @@ -708,8 +772,8 @@ class SARA_R5 : public Print //SARA_R5_error_t gpsGetPos(struct PositionData *pos); //SARA_R5_error_t gpsEnableSat(bool enable = true); //SARA_R5_error_t gpsGetSat(uint8_t *sats); - SARA_R5_error_t gpsEnableRmc(bool enable = true); - SARA_R5_error_t gpsGetRmc(struct PositionData *pos, struct SpeedData *speed, struct ClockData *clk, bool *valid); + SARA_R5_error_t gpsEnableRmc(bool enable = true); // Enable GPRMC messages + SARA_R5_error_t gpsGetRmc(struct PositionData *pos, struct SpeedData *speed, struct ClockData *clk, bool *valid); //Parse a GPRMC message //SARA_R5_error_t gpsEnableSpeed(bool enable = true); //SARA_R5_error_t gpsGetSpeed(struct SpeedData *speed); @@ -749,8 +813,16 @@ class SARA_R5 : public Print uint8_t _maxInitDepth; uint8_t _currentInitDepth = 0; + #define _RXBuffSize 2056 + const unsigned long _rxWindowMillis = 2; // 1ms is not quite long enough for a single char at 9600 baud. millis roll over much less often than micros. + char _saraRXBuffer[_RXBuffSize]; + char _pruneBuffer[_RXBuffSize]; + char _saraResponseBacklog[_RXBuffSize]; + int _saraResponseBacklogLength = 0; // The backlog could contain binary data so we can't use strlen to find its length + void (*_socketListenCallback)(int, IPAddress, unsigned int, int, IPAddress, unsigned int); void (*_socketReadCallback)(int, String); + void (*_socketReadCallbackPlus)(int, const char *, int, IPAddress, int); // socket, data, length, remoteAddress, remotePort void (*_socketCloseCallback)(int); void (*_gpsRequestCallback)(ClockData, PositionData, SpeedData, unsigned long); void (*_simStateReportCallback)(SARA_R5_sim_states_t); @@ -758,6 +830,8 @@ class SARA_R5 : public Print void (*_pingRequestCallback)(int, int, String, IPAddress, int, long); void (*_httpCommandRequestCallback)(int, int, int); + int _lastSocketProtocol[SARA_R5_NUM_SOCKETS]; // Record the protocol for each socket to avoid having to call querySocketType in parseSocketReadIndication + typedef enum { SARA_R5_INIT_STANDARD, @@ -780,10 +854,10 @@ class SARA_R5 : public Print // Send command with an expected (potentially partial) response, store entire response SARA_R5_error_t sendCommandWithResponse(const char *command, const char *expectedResponse, - char *responseDest, unsigned long commandTimeout, bool at = true); + char *responseDest, unsigned long commandTimeout, int destSize = minimumResponseAllocation, bool at = true); // Send a command -- prepend AT if at is true - int sendCommand(const char *command, bool at); + void sendCommand(const char *command, bool at); SARA_R5_error_t parseSocketReadIndication(int socket, int length); SARA_R5_error_t parseSocketReadIndicationUDP(int socket, int length);