From d2d822eb7db5e0bcd508cda7f8f9f55a97b35780 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 4 Jan 2022 10:48:43 +0000 Subject: [PATCH 01/17] Correct the pdpStr memcpy lengths in setAPN --- src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index 0e58270..264caee 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -1261,13 +1261,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); From 8fa3c60d0b47e391ae38bd8cc36b6757b136a58e Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 4 Jan 2022 11:19:41 +0000 Subject: [PATCH 02/17] Make the virtual write consistent with hwWriteData --- src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index 264caee..c242bfd 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -536,18 +536,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; From 1fb3cc8b72b0d6a5ffe44326599539f538329fef Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 4 Jan 2022 12:04:59 +0000 Subject: [PATCH 03/17] Correct socketWrite for binary data. Add IPAddress overloads for socketWriteUDP and socketConnect. --- ...parkFun_u-blox_SARA-R5_Arduino_Library.cpp | 52 ++++++++++++++++--- src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 4 +- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index c242bfd..9b90eb1 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -2161,7 +2161,19 @@ 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; @@ -2177,7 +2189,8 @@ SARA_R5_error_t SARA_R5::socketWrite(int socket, const char *str) 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); @@ -2188,12 +2201,26 @@ SARA_R5_error_t SARA_R5::socketWrite(int socket, const char *str) while (millis() < (writeDelay + 50)) ; //uBlox specification says to wait 50ms after receiving "@" to write data. - if (_printDebug == true) + if (len == -1) { - _debugPort->print(F("socketWrite: writing: ")); - _debugPort->println(str); + if (_printDebug == true) + { + _debugPort->print(F("socketWrite: writing: ")); + _debugPort->println(str); + } + hwPrint(str); } - hwPrint(str); + else + { + if (_printDebug == true) + { + _debugPort->print(F("socketWrite: writing ")); + _debugPort->print(len); + _debugPort->println(F(" bytes")); + } + hwWriteData(str, len); + } + err = waitForResponse(SARA_R5_RESPONSE_OK, SARA_R5_RESPONSE_ERROR, SARA_R5_SOCKET_WRITE_TIMEOUT); } @@ -2216,7 +2243,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) @@ -2265,6 +2292,17 @@ 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, 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, int len) { return socketWriteUDP(socket, address.c_str(), port, str.c_str(), len); diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index 7ed099d..238f997 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -636,9 +636,11 @@ class SARA_R5 : public Print 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 socketConnect(int socket, IPAddress address, unsigned int port); + SARA_R5_error_t socketWrite(int socket, const char *str, int len = -1); SARA_R5_error_t socketWrite(int socket, String str); 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, IPAddress 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 socketRead(int socket, int length, char *readDest); SARA_R5_error_t socketReadUDP(int socket, int length, char *readDest); From 34ffb80bbe8ee33fc272d7e4a73e668685548dfc Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 4 Jan 2022 13:19:12 +0000 Subject: [PATCH 04/17] Update socketReadUDP. Correct socketRead for binary data. --- ...parkFun_u-blox_SARA-R5_Arduino_Library.cpp | 131 ++++++++++++++++-- src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 2 +- 2 files changed, 118 insertions(+), 15 deletions(-) diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index 9b90eb1..9406ff9 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -2315,8 +2315,19 @@ 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; + int readLength = 0; + int socketStore = 0; - command = sara_r5_calloc_char(strlen(SARA_R5_READ_SOCKET) + 8); + // 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) + 32); if (command == NULL) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,%d", SARA_R5_READ_SOCKET, socket, length); @@ -2333,6 +2344,36 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) if (err == SARA_R5_ERROR_SUCCESS) { + // Extract the data - and check the quote is present + scanNum = sscanf(response, "+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. + if (readLength != length) + { + if (_printDebug == true) + { + _debugPort->print(F("socketRead: error: length=")); + _debugPort->print(length); + _debugPort->print(F(" readLength=")); + _debugPort->println(readLength); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + // Find the first double-quote: strBegin = strchr(response, '\"'); @@ -2343,11 +2384,15 @@ 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 < length) { readDest[readIndex] = strBegin[1 + readIndex]; - readIndex += 1; + readIndex++; } + + if (_printDebug == true) + _debugPort->println(F("socketRead: success")); } free(command); @@ -2356,15 +2401,28 @@ 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; + 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); @@ -2381,14 +2439,38 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest) 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 + scanNum = sscanf(response, "+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. + if (readLength != length) + { + if (_printDebug == true) + { + _debugPort->print(F("socketReadUDP: error: length=")); + _debugPort->print(length); + _debugPort->print(F(" readLength=")); + _debugPort->println(readLength); + } + free(command); + free(response); + return SARA_R5_ERROR_UNEXPECTED_RESPONSE; + } + + // Find the third double-quote strBegin = strchr(response, '\"'); strBegin = strchr(strBegin + 1, '\"'); strBegin = strchr(strBegin + 1, '\"'); @@ -2400,11 +2482,32 @@ 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 < length) { 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); diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index 238f997..775765f 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -643,7 +643,7 @@ class SARA_R5 : public Print 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, int len = -1); SARA_R5_error_t socketRead(int socket, int length, char *readDest); - SARA_R5_error_t socketReadUDP(int socket, int length, char *readDest); + SARA_R5_error_t socketReadUDP(int socket, int length, char *readDest, IPAddress *remoteIPAddress = NULL, int *remotePort = NULL); SARA_R5_error_t socketListen(int socket, unsigned int port); SARA_R5_error_t socketDirectLinkMode(int socket); SARA_R5_error_t socketDirectLinkTimeTrigger(int socket, unsigned long timerTrigger); From d0d6428307326ac5a3ff4cb7524906f8c6b12204 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 4 Jan 2022 13:36:05 +0000 Subject: [PATCH 05/17] Add socketReadAvailable and socketReadAvailableUDP --- keywords.txt | 2 + ...parkFun_u-blox_SARA-R5_Arduino_Library.cpp | 98 +++++++++++++++++++ src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 2 + 3 files changed, 102 insertions(+) diff --git a/keywords.txt b/keywords.txt index 87e1598..0a729c5 100644 --- a/keywords.txt +++ b/keywords.txt @@ -106,7 +106,9 @@ socketConnect KEYWORD2 socketWrite KEYWORD2 socketWriteUDP KEYWORD2 socketRead KEYWORD2 +socketReadAvailable KEYWORD2 socketReadUDP KEYWORD2 +socketReadAvailableUDP KEYWORD2 socketListen KEYWORD2 socketDirectLinkMode KEYWORD2 socketDirectLinkTimeTrigger KEYWORD2 diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index 9406ff9..d819b5b 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -2401,6 +2401,55 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) return err; } +SARA_R5_error_t SARA_R5::socketReadAvailable(int socket, int *length) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum; + 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(strlen(SARA_R5_READ_SOCKET) + 16); + 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) + { + scanNum = sscanf(response, "+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); + free(response); + + return err; +} + SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest, IPAddress *remoteIPAddress, int *remotePort) { char *command; @@ -2516,6 +2565,55 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest, I return err; } +SARA_R5_error_t SARA_R5::socketReadAvailableUDP(int socket, int *length) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum; + 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(strlen(SARA_R5_READ_UDP_SOCKET) + 16); + 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) + { + scanNum = sscanf(response, "+USORF: %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); + free(response); + + return err; +} + SARA_R5_error_t SARA_R5::socketListen(int socket, unsigned int port) { SARA_R5_error_t err; diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index 775765f..1549e71 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -643,7 +643,9 @@ class SARA_R5 : public Print 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, int len = -1); SARA_R5_error_t socketRead(int socket, int length, char *readDest); + SARA_R5_error_t socketReadAvailable(int socket, int *length); SARA_R5_error_t socketReadUDP(int socket, int length, char *readDest, IPAddress *remoteIPAddress = NULL, int *remotePort = NULL); + SARA_R5_error_t socketReadAvailableUDP(int socket, int *length); SARA_R5_error_t socketListen(int socket, unsigned int port); SARA_R5_error_t socketDirectLinkMode(int socket); SARA_R5_error_t socketDirectLinkTimeTrigger(int socket, unsigned long timerTrigger); From 7b8dc051560a8110c0722b9e841913474457563e Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 4 Jan 2022 16:44:36 +0000 Subject: [PATCH 06/17] Add socketReadCallbackPlus - provides proper support for UDP & binary TCP data --- keywords.txt | 1 + ...parkFun_u-blox_SARA-R5_Arduino_Library.cpp | 32 +++++++++++++++++-- src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 2 ++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/keywords.txt b/keywords.txt index 0a729c5..2abd344 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 diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index d819b5b..ed271cc 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; @@ -473,6 +474,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; @@ -4011,7 +4017,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); @@ -4022,6 +4038,8 @@ 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)) { @@ -4034,7 +4052,7 @@ SARA_R5_error_t SARA_R5::parseSocketReadIndicationUDP(int socket, int length) 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); @@ -4043,7 +4061,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); diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index 1549e71..17d9f93 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -488,6 +488,7 @@ class SARA_R5 : public Print // Callbacks (called during polling) void setSocketListenCallback(void (*socketListenCallback)(int, IPAddress, unsigned int, int, IPAddress, unsigned int)); void setSocketReadCallback(void (*socketReadCallback)(int, String)); + void setSocketReadCallbackPlus(void (*socketReadCallbackPlus)(int, const char *, int, IPAddress, int)); // socket, data, length, remoteAddress, remotePort void setSocketCloseCallback(void (*socketCloseCallback)(int)); void setGpsReadCallback(void (*gpsRequestCallback)(ClockData time, PositionData gps, SpeedData spd, unsigned long uncertainty)); @@ -755,6 +756,7 @@ class SARA_R5 : public Print 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); From 2d4e399839cec80447cef4495d1be234c202ea5f Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 4 Jan 2022 17:45:27 +0000 Subject: [PATCH 07/17] Add querySocket functions --- keywords.txt | 7 + ...parkFun_u-blox_SARA-R5_Arduino_Library.cpp | 348 ++++++++++++++++++ src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 23 ++ 3 files changed, 378 insertions(+) diff --git a/keywords.txt b/keywords.txt index 2abd344..15b606e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -116,6 +116,13 @@ 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 diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index ed271cc..45abba5 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -2738,6 +2738,354 @@ SARA_R5_error_t SARA_R5::socketDirectLinkCongestionTimer(int socket, unsigned lo return err; } +SARA_R5_error_t SARA_R5::querySocketType(int socket, SARA_R5_socket_protocol_t *protocol) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum; + 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,0", SARA_R5_SOCKET_CONTROL, socket); + + response = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + 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) + { + scanNum = sscanf(response, "+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; + } + + free(command); + free(response); + + return err; +} + +SARA_R5_error_t SARA_R5::querySocketLastError(int socket, int *error) +{ + char *command; + char *response; + SARA_R5_error_t err; + int scanNum; + 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,1", SARA_R5_SOCKET_CONTROL, socket); + + response = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + 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) + { + scanNum = sscanf(response, "+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; + 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(strlen(SARA_R5_SOCKET_CONTROL) + 16); + 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) + { + scanNum = sscanf(response, "+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; + 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(strlen(SARA_R5_SOCKET_CONTROL) + 16); + 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) + { + scanNum = sscanf(response, "+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; + 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(strlen(SARA_R5_SOCKET_CONTROL) + 16); + 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) + { + scanNum = sscanf(response, "+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; + 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(strlen(SARA_R5_SOCKET_CONTROL) + 16); + 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) + { + scanNum = sscanf(response, "+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; + 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(strlen(SARA_R5_SOCKET_CONTROL) + 16); + 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) + { + scanNum = sscanf(response, "+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() { diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index 17d9f93..6163262 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -144,6 +144,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 @@ -653,6 +654,27 @@ class SARA_R5 : public Print 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); + 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); + typedef enum + { + TCP_SOCKET_STATUS_INACTIVE, + TCP_SOCKET_STATUS_LISTEN, + TCP_SOCKET_STATUS_SYN_SENT, + TCP_SOCKET_STATUS_SYN_RCVD, + TCP_SOCKET_STATUS_ESTABLISHED, + TCP_SOCKET_STATUS_FIN_WAIT_1, + TCP_SOCKET_STATUS_FIN_WAIT_2, + TCP_SOCKET_STATUS_CLOSE_WAIT, + TCP_SOCKET_STATUS_CLOSING, + TCP_SOCKET_STATUS_LAST_ACK, + TCP_SOCKET_STATUS_TIME_WAIT + } SARA_R5_tcp_socket_status_t; + SARA_R5_error_t querySocketStatusTCP(int socket, SARA_R5_tcp_socket_status_t *status); + SARA_R5_error_t querySocketOutUnackData(int socket, uint32_t *total); int socketGetLastError(); IPAddress lastRemoteIP(void); @@ -680,6 +702,7 @@ class SARA_R5 : public Print 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 getNetworkAssignedIPAddress(int profile, IPAddress *address); // GPS typedef enum From 3491da0ef8504cdc97265a43c6cb35a3c6a57e58 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 4 Jan 2022 17:55:50 +0000 Subject: [PATCH 08/17] Add getNetworkAssignedIPAddress --- ...parkFun_u-blox_SARA-R5_Arduino_Library.cpp | 52 +++++++++++++++++++ src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 3 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index 45abba5..0886fac 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -3509,6 +3509,58 @@ 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; + int profileStore = 0; + 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,0", SARA_R5_NETWORK_ASSIGNED_DATA, profile); + + response = sara_r5_calloc_char(strlen(SARA_R5_NETWORK_ASSIGNED_DATA) + 32); + 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) + { + scanNum = sscanf(response, "+UPSND: %d,0,\"%d.%d.%d.%d\"", + &profileStore, + ¶mVals[0], ¶mVals[1], ¶mVals[2], ¶mVals[3]); + if (scanNum != 5) + { + 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; diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index 6163262..01f94f1 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -131,6 +131,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 @@ -702,7 +703,7 @@ class SARA_R5 : public Print 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 getNetworkAssignedIPAddress(int profile, IPAddress *address); + SARA_R5_error_t getNetworkAssignedIPAddress(int profile, IPAddress *address); // Get the dynamic IP address assigned during PDP context activation // GPS typedef enum From 204b666fe7b8f71454e67e8299fd5114537cd5a2 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Tue, 4 Jan 2022 17:57:26 +0000 Subject: [PATCH 09/17] Update keywords.txt --- keywords.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/keywords.txt b/keywords.txt index 15b606e..589cded 100644 --- a/keywords.txt +++ b/keywords.txt @@ -140,6 +140,7 @@ sendHTTPPOSTdata KEYWORD2 setPDPconfiguration KEYWORD2 performPDPaction KEYWORD2 activatePDPcontext KEYWORD2 +getNetworkAssignedIPAddress KEYWORD2 isGPSon KEYWORD2 gpsPower KEYWORD2 # gpsEnableClock KEYWORD2 From 2add3754e2464a5127c9bd4f0ca776103180516d Mon Sep 17 00:00:00 2001 From: PaulZC Date: Wed, 5 Jan 2022 14:29:32 +0000 Subject: [PATCH 10/17] Add comments. Enhance sendCommandWithResponse so it cannot overrun the destination. Default all response memory allocations to minimumResponseAllocation (128). --- keywords.txt | 11 + ...parkFun_u-blox_SARA-R5_Arduino_Library.cpp | 206 ++++++++++-------- src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 83 ++++--- 3 files changed, 187 insertions(+), 113 deletions(-) diff --git a/keywords.txt b/keywords.txt index 589cded..d1e71a2 100644 --- a/keywords.txt +++ b/keywords.txt @@ -347,6 +347,17 @@ EXT_GNSS_TIMESTAMP LITERAL1 DTR_MODE LITERAL1 KHZ_32768_OUT LITERAL1 PAD_DISABLED LITERAL1 +TCP_SOCKET_STATUS_INACTIVE LITERAL1 +TCP_SOCKET_STATUS_LISTEN LITERAL1 +TCP_SOCKET_STATUS_SYN_SENT LITERAL1 +TCP_SOCKET_STATUS_SYN_RCVD LITERAL1 +TCP_SOCKET_STATUS_ESTABLISHED LITERAL1 +TCP_SOCKET_STATUS_FIN_WAIT_1 LITERAL1 +TCP_SOCKET_STATUS_FIN_WAIT_2 LITERAL1 +TCP_SOCKET_STATUS_CLOSE_WAIT LITERAL1 +TCP_SOCKET_STATUS_CLOSING LITERAL1 +TCP_SOCKET_STATUS_LAST_ACK LITERAL1 +TCP_SOCKET_STATUS_TIME_WAIT LITERAL1 GNSS_SYSTEM_GPS LITERAL1 GNSS_SYSTEM_SBAS LITERAL1 GNSS_SYSTEM_GALILEO LITERAL1 diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index 0886fac..fcd2f8f 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -158,9 +158,10 @@ 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) @@ -171,7 +172,7 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - { + { // URC: +UUSORF (Receive From command (UDP only)) int socket, length; int ret = sscanf(event, "+UUSORF: %d,%d", &socket, &length); if (ret == 2) @@ -182,7 +183,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; @@ -212,7 +213,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) @@ -229,7 +230,7 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - { + { // URC: +UULOC (Localization information - CellLocate and hybrid positioning) ClockData clck; PositionData gps; SpeedData spd; @@ -300,7 +301,7 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - { + { // URC: +UUSIMSTAT (SIM Status) SARA_R5_sim_states_t state; int scanNum; int stateStore; @@ -322,7 +323,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; @@ -349,7 +350,7 @@ bool SARA_R5::processURCEvent(const char *event) return true; } } - { + { // URC: +UUHTTPCR (HTTP Command Result) int profile, command, result; int scanNum; @@ -371,8 +372,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; @@ -595,7 +595,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); @@ -616,7 +616,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); @@ -637,7 +637,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); @@ -658,7 +658,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); @@ -679,7 +679,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); @@ -700,7 +700,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); @@ -721,7 +721,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); @@ -742,7 +742,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); @@ -763,7 +763,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); @@ -815,7 +815,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); @@ -872,7 +872,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); @@ -941,7 +941,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); @@ -1006,7 +1006,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); @@ -1069,7 +1069,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); @@ -1129,7 +1129,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); @@ -1137,7 +1137,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); @@ -1167,7 +1168,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); @@ -1175,7 +1176,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); @@ -1327,7 +1329,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) { @@ -1442,7 +1444,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); @@ -1521,7 +1523,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); @@ -1530,7 +1533,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) @@ -1640,7 +1643,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); @@ -1762,7 +1765,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); } @@ -1788,7 +1791,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); @@ -1849,7 +1852,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) { @@ -2023,7 +2026,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); @@ -2069,7 +2072,7 @@ int SARA_R5::socketOpen(SARA_R5_socket_protocol_t protocol, unsigned int localPo else sprintf(command, "%s=%d,%d", SARA_R5_CREATE_SOCKET, protocol, localPort); - response = sara_r5_calloc_char(128); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { if (_printDebug == true) @@ -2127,7 +2130,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); @@ -2189,7 +2192,7 @@ SARA_R5_error_t SARA_R5::socketWrite(int socket, const char *str, int len) 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); @@ -2262,7 +2265,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); @@ -2338,7 +2341,8 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) 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) + 128; + response = sara_r5_calloc_char(responseLength); if (response == NULL) { free(command); @@ -2346,12 +2350,12 @@ 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 - scanNum = sscanf(response, "+USORD: %d,%d,\"", + scanNum = sscanf(response, "\r\n+USORD: %d,%d,\"", &socketStore, &readLength); if (scanNum != 2) { @@ -2421,7 +2425,7 @@ SARA_R5_error_t SARA_R5::socketReadAvailable(int socket, int *length) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,0", SARA_R5_READ_SOCKET, socket); - response = sara_r5_calloc_char(strlen(SARA_R5_READ_SOCKET) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2433,7 +2437,7 @@ SARA_R5_error_t SARA_R5::socketReadAvailable(int socket, int *length) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "+USORD: %d,%d", + scanNum = sscanf(response, "\r\n+USORD: %d,%d", &socketStore, &readLength); if (scanNum != 2) { @@ -2482,7 +2486,8 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest, I 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) + 128; + response = sara_r5_calloc_char(responseLength); if (response == NULL) { free(command); @@ -2490,12 +2495,12 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest, I } 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 third quote is present - scanNum = sscanf(response, "+USORF: %d,\"%d.%d.%d.%d\",%d,%d,\"", + scanNum = sscanf(response, "\r\n+USORF: %d,\"%d.%d.%d.%d\",%d,%d,\"", &socketStore, &remoteIPstore[0], &remoteIPstore[1], &remoteIPstore[2], &remoteIPstore[3], &portStore, &readLength); if (scanNum != 7) @@ -2585,7 +2590,7 @@ SARA_R5_error_t SARA_R5::socketReadAvailableUDP(int socket, int *length) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,0", SARA_R5_READ_UDP_SOCKET, socket); - response = sara_r5_calloc_char(strlen(SARA_R5_READ_UDP_SOCKET) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2597,7 +2602,7 @@ SARA_R5_error_t SARA_R5::socketReadAvailableUDP(int socket, int *length) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "+USORF: %d,%d", + scanNum = sscanf(response, "\r\n+USORF: %d,%d", &socketStore, &readLength); if (scanNum != 2) { @@ -2738,7 +2743,7 @@ SARA_R5_error_t SARA_R5::socketDirectLinkCongestionTimer(int socket, unsigned lo return err; } -SARA_R5_error_t SARA_R5::querySocketType(int socket, SARA_R5_socket_protocol_t *protocol) +SARA_R5_error_t SARA_R5::querySocketType(int socket, int *protocol) { char *command; char *response; @@ -2752,7 +2757,7 @@ SARA_R5_error_t SARA_R5::querySocketType(int socket, SARA_R5_socket_protocol_t * return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,0", SARA_R5_SOCKET_CONTROL, socket); - response = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2764,7 +2769,7 @@ SARA_R5_error_t SARA_R5::querySocketType(int socket, SARA_R5_socket_protocol_t * if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "+USOCTL: %d,0,%d", + scanNum = sscanf(response, "\r\n+USOCTL: %d,0,%d", &socketStore, ¶mVal); if (scanNum != 2) { @@ -2778,7 +2783,7 @@ SARA_R5_error_t SARA_R5::querySocketType(int socket, SARA_R5_socket_protocol_t * return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } - *protocol = (SARA_R5_socket_protocol_t)paramVal; + *protocol = paramVal; } free(command); @@ -2801,7 +2806,7 @@ SARA_R5_error_t SARA_R5::querySocketLastError(int socket, int *error) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,1", SARA_R5_SOCKET_CONTROL, socket); - response = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2813,7 +2818,7 @@ SARA_R5_error_t SARA_R5::querySocketLastError(int socket, int *error) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "+USOCTL: %d,1,%d", + scanNum = sscanf(response, "\r\n+USOCTL: %d,1,%d", &socketStore, ¶mVal); if (scanNum != 2) { @@ -2850,7 +2855,7 @@ SARA_R5_error_t SARA_R5::querySocketTotalBytesSent(int socket, uint32_t *total) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,2", SARA_R5_SOCKET_CONTROL, socket); - response = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2862,7 +2867,7 @@ SARA_R5_error_t SARA_R5::querySocketTotalBytesSent(int socket, uint32_t *total) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "+USOCTL: %d,2,%lu", + scanNum = sscanf(response, "\r\n+USOCTL: %d,2,%lu", &socketStore, ¶mVal); if (scanNum != 2) { @@ -2899,7 +2904,7 @@ SARA_R5_error_t SARA_R5::querySocketTotalBytesReceived(int socket, uint32_t *tot return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,3", SARA_R5_SOCKET_CONTROL, socket); - response = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2911,7 +2916,7 @@ SARA_R5_error_t SARA_R5::querySocketTotalBytesReceived(int socket, uint32_t *tot if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "+USOCTL: %d,3,%lu", + scanNum = sscanf(response, "\r\n+USOCTL: %d,3,%lu", &socketStore, ¶mVal); if (scanNum != 2) { @@ -2948,7 +2953,7 @@ SARA_R5_error_t SARA_R5::querySocketRemoteIPAddress(int socket, IPAddress *addre return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,4", SARA_R5_SOCKET_CONTROL, socket); - response = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -2960,7 +2965,7 @@ SARA_R5_error_t SARA_R5::querySocketRemoteIPAddress(int socket, IPAddress *addre if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "+USOCTL: %d,4,\"%d.%d.%d.%d\",%d", + scanNum = sscanf(response, "\r\n+USOCTL: %d,4,\"%d.%d.%d.%d\",%d", &socketStore, ¶mVals[0], ¶mVals[1], ¶mVals[2], ¶mVals[3], ¶mVals[4]); @@ -2988,7 +2993,7 @@ SARA_R5_error_t SARA_R5::querySocketRemoteIPAddress(int socket, IPAddress *addre return err; } -SARA_R5_error_t SARA_R5::querySocketStatusTCP(int socket, SARA_R5_tcp_socket_status_t *status) +SARA_R5_error_t SARA_R5::querySocketStatusTCP(int socket, int *status) { char *command; char *response; @@ -3002,7 +3007,7 @@ SARA_R5_error_t SARA_R5::querySocketStatusTCP(int socket, SARA_R5_tcp_socket_sta return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,10", SARA_R5_SOCKET_CONTROL, socket); - response = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -3014,7 +3019,7 @@ SARA_R5_error_t SARA_R5::querySocketStatusTCP(int socket, SARA_R5_tcp_socket_sta if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "+USOCTL: %d,10,%d", + scanNum = sscanf(response, "\r\n+USOCTL: %d,10,%d", &socketStore, ¶mVal); if (scanNum != 2) { @@ -3028,7 +3033,7 @@ SARA_R5_error_t SARA_R5::querySocketStatusTCP(int socket, SARA_R5_tcp_socket_sta return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } - *status = (SARA_R5_tcp_socket_status_t)paramVal; + *status = paramVal; } free(command); @@ -3051,7 +3056,7 @@ SARA_R5_error_t SARA_R5::querySocketOutUnackData(int socket, uint32_t *total) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,11", SARA_R5_SOCKET_CONTROL, socket); - response = sara_r5_calloc_char(strlen(SARA_R5_SOCKET_CONTROL) + 16); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -3063,7 +3068,7 @@ SARA_R5_error_t SARA_R5::querySocketOutUnackData(int socket, uint32_t *total) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "+USOCTL: %d,11,%lu", + scanNum = sscanf(response, "\r\n+USOCTL: %d,11,%lu", &socketStore, ¶mVal); if (scanNum != 2) { @@ -3098,7 +3103,7 @@ int SARA_R5::socketGetLastError() 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); @@ -3112,7 +3117,7 @@ int SARA_R5::socketGetLastError() if (err == SARA_R5_ERROR_SUCCESS) { - sscanf(response, "+USOER: %d", &errorCode); + sscanf(response, "\r\n+USOER: %d", &errorCode); } free(command); @@ -3371,7 +3376,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); @@ -3514,16 +3519,17 @@ SARA_R5_error_t SARA_R5::getNetworkAssignedIPAddress(int profile, IPAddress *add char *command; char *response; SARA_R5_error_t err; - int scanNum; + 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,0", SARA_R5_NETWORK_ASSIGNED_DATA, profile); + sprintf(command, "%s=%d,%d", SARA_R5_NETWORK_ASSIGNED_DATA, profile, paramTag); - response = sara_r5_calloc_char(strlen(SARA_R5_NETWORK_ASSIGNED_DATA) + 32); + response = sara_r5_calloc_char(minimumResponseAllocation); if (response == NULL) { free(command); @@ -3535,10 +3541,12 @@ SARA_R5_error_t SARA_R5::getNetworkAssignedIPAddress(int profile, IPAddress *add if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "+UPSND: %d,0,\"%d.%d.%d.%d\"", - &profileStore, - ¶mVals[0], ¶mVals[1], ¶mVals[2], ¶mVals[3]); - if (scanNum != 5) + 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 ((searchPtr == NULL) || (scanNum != 6)) { if (_printDebug == true) { @@ -3573,7 +3581,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); @@ -3733,7 +3741,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); @@ -3853,7 +3861,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); @@ -4167,7 +4175,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); @@ -4281,7 +4289,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; @@ -4311,7 +4319,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]) @@ -4357,7 +4378,8 @@ 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) @@ -4404,6 +4426,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; @@ -4417,7 +4443,7 @@ SARA_R5_error_t SARA_R5::parseSocketReadIndication(int socket, int length) if (_socketReadCallback != NULL) { - String dataAsString; // Create an empty string + 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); @@ -4446,6 +4472,10 @@ SARA_R5_error_t SARA_R5::parseSocketReadIndicationUDP(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) { @@ -4461,7 +4491,7 @@ SARA_R5_error_t SARA_R5::parseSocketReadIndicationUDP(int socket, int length) if (_socketReadCallback != NULL) { - String dataAsString; // Create an empty string + 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); diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index 01f94f1..a469415 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -181,6 +181,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 @@ -454,10 +458,12 @@ class SARA_R5 : public Print 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 @@ -474,24 +480,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 setSocketReadCallbackPlus(void (*socketReadCallbackPlus)(int, const char *, int, IPAddress, int)); // socket, data, length, remoteAddress, remotePort - 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)); @@ -522,17 +533,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); @@ -636,26 +647,44 @@ 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); + 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); + 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, IPAddress 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); + // 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); + // 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); - SARA_R5_error_t querySocketType(int socket, SARA_R5_socket_protocol_t *protocol); + // Use +USOCTL (Socket control) to query the socket parameters + SARA_R5_error_t querySocketType(int socket, int *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); @@ -674,9 +703,12 @@ class SARA_R5 : public Print TCP_SOCKET_STATUS_LAST_ACK, TCP_SOCKET_STATUS_TIME_WAIT } SARA_R5_tcp_socket_status_t; - SARA_R5_error_t querySocketStatusTCP(int socket, SARA_R5_tcp_socket_status_t *status); + SARA_R5_error_t querySocketStatusTCP(int socket, int *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 @@ -697,13 +729,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 getNetworkAssignedIPAddress(int profile, IPAddress *address); // Get the dynamic IP address assigned during PDP context activation + 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 @@ -737,8 +770,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); @@ -810,7 +843,7 @@ 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); From 0389ec548d6fdf7502766ba39c92703ef54dc168 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Wed, 5 Jan 2022 14:33:48 +0000 Subject: [PATCH 11/17] Update the socket ping pong examples --- .../SARA-R5_Example10_SocketPingPong.ino | 202 ++++--- ...-R5_Example10_SocketPingPong_BinaryTCP.ino | 567 ++++++++++++++++++ 2 files changed, 676 insertions(+), 93 deletions(-) create mode 100644 examples/SARA-R5_Example10_SocketPingPong_BinaryTCP/SARA-R5_Example10_SocketPingPong_BinaryTCP.ino 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..f58d9c3 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 @@ -85,6 +86,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 @@ -212,7 +219,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) ; @@ -253,86 +260,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 +285,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()); // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- @@ -378,9 +319,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 +342,7 @@ void setup() selection = 0; } } - else + else if ((c >= '0') && (c <= '9')) { selection = c - '0'; // Store a single digit } @@ -443,17 +384,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 +407,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 +458,8 @@ void setup() } +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + void loop() { mySARA.bufferedPoll(); // Process the backlog (if any) and any fresh serial data @@ -527,6 +468,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 +479,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: ")); + int 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): ")); + int 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..2a2766c --- /dev/null +++ b/examples/SARA-R5_Example10_SocketPingPong_BinaryTCP/SARA-R5_Example10_SocketPingPong_BinaryTCP.ino @@ -0,0 +1,567 @@ +/* + + 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 + Grab yourself some simple TCP Echo Server code: + The third example here works well: https://rosettacode.org/wiki/Echo_server#Python + 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 + 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: + 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" + 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" + { + const char ping[] = { 0x00, 0x01, 0x02, 0x03 }; + mySARA.socketWrite(socket, ping, 4); // 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()) +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, 9600) ) + { + 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 + 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: ")); + int 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): ")); + int 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); +} From 99c94553f657a73d0296443ad78d88958e35d541 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Wed, 5 Jan 2022 16:38:19 +0000 Subject: [PATCH 12/17] Update SARA-R5_Example10_SocketPingPong_BinaryTCP.ino --- .../SARA-R5_Example10_SocketPingPong_BinaryTCP.ino | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) 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 index 2a2766c..77e8567 100644 --- 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 @@ -171,8 +171,18 @@ void processSocketData(int socket, String theData) if ((theData[0] == 0x04) && (theData[1] == 0x05) && (theData[2] == 0x06) && (theData[3] == 0x07)) // Look for the "Pong" { - const char ping[] = { 0x00, 0x01, 0x02, 0x03 }; - mySARA.socketWrite(socket, ping, 4); // Send the "Ping" + // 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++; } From 91800eae7d842df3b122c5541a200eff83efbd88 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Wed, 5 Jan 2022 18:34:18 +0000 Subject: [PATCH 13/17] Remove the len parameter from the String version of socketWriteUDP --- src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp | 4 ++-- src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index fcd2f8f..d506103 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -2312,9 +2312,9 @@ SARA_R5_error_t SARA_R5::socketWriteUDP(int socket, IPAddress address, int port, return (socketWriteUDP(socket, (const char *)charAddress, port, str, len)); } -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, 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) diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index a469415..e9fe827 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -660,7 +660,7 @@ class SARA_R5 : public Print // 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, IPAddress 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, 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 From 339e90aafc8ca8d78c36462d7a493b74791d2311 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Fri, 7 Jan 2022 13:28:17 +0000 Subject: [PATCH 14/17] Significant updates: see below for full details Made the RXBuffer and backlog buffer private. Added _lastSocketProtocol - to allow bufferedPoll to easily distinguish between URC's from TCP and UDP sockets. Discovered that UDP traffic can generate +UUSORD URCs instead of +UUSORF ! Added _saraResponseBacklogLength. Discovered that strtok is not reentrant... Switched to strtok_r ! Prevented any binary \0's from getting into the backlog (and causing badness for strlen, strtok, etc.) Swapped from micros to millis for the serial receive timeouts. (millis overflows far less often...) Made the sscanf's as consistent as possible. Switched to using strstr followed by sscanf, allowing the preceding \r\n to be ignored. Added new TCP and UDP examples - including multi-socket. Added the Python Multi_Echo Utils. --- Utils/Multi_TCP_Echo.py | 74 +++ Utils/Multi_UDP_Echo.py | 40 ++ .../SARA-R5_Example10_SocketPingPong.ino | 41 +- ...-R5_Example10_SocketPingPong_BinaryTCP.ino | 41 +- ...5_Example10_SocketPingPong_MultipleTCP.ino | 606 ++++++++++++++++++ ...5_Example10_SocketPingPong_MultipleUDP.ino | 424 ++++++++++++ keywords.txt | 22 +- ...parkFun_u-blox_SARA-R5_Arduino_Library.cpp | 466 +++++++++----- src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 50 +- 9 files changed, 1518 insertions(+), 246 deletions(-) create mode 100644 Utils/Multi_TCP_Echo.py create mode 100644 Utils/Multi_UDP_Echo.py create mode 100644 examples/SARA-R5_Example10_SocketPingPong_MultipleTCP/SARA-R5_Example10_SocketPingPong_MultipleTCP.ino create mode 100644 examples/SARA-R5_Example10_SocketPingPong_MultipleUDP/SARA-R5_Example10_SocketPingPong_MultipleUDP.ino 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 f58d9c3..ec4ffd7 100644 --- a/examples/SARA-R5_Example10_SocketPingPong/SARA-R5_Example10_SocketPingPong.ino +++ b/examples/SARA-R5_Example10_SocketPingPong/SARA-R5_Example10_SocketPingPong.ino @@ -31,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: @@ -40,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" @@ -179,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(); @@ -233,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!")); } @@ -310,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); // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- @@ -496,7 +497,7 @@ void printSocketParameters(int socket) Serial.println(F("Socket parameters:")); Serial.print(F("Socket type: ")); - int socketType; + SARA_R5_socket_protocol_t socketType; mySARA.querySocketType(socket, &socketType); if (socketType == SARA_R5_TCP) Serial.println(F("TCP")); @@ -527,29 +528,29 @@ void printSocketParameters(int socket) Serial.println(remoteAddress.toString()); Serial.print(F("Socket status (TCP sockets only): ")); - int socketStatus; + SARA_R5_tcp_socket_status_t socketStatus; mySARA.querySocketStatusTCP(socket, &socketStatus); - if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_INACTIVE) + if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_INACTIVE) Serial.println(F("INACTIVE")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_LISTEN) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_LISTEN) Serial.println(F("LISTEN")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_SYN_SENT) + 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) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_SYN_RCVD) Serial.println(F("SYN_RCVD")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_ESTABLISHED) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_ESTABLISHED) Serial.println(F("ESTABLISHED")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_FIN_WAIT_1) + 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) + 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) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_CLOSE_WAIT) Serial.println(F("CLOSE_WAIT")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_CLOSING) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_CLOSING) Serial.println(F("CLOSING")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_LAST_ACK) + 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) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_TIME_WAIT) Serial.println(F("TIME_WAIT")); else Serial.println(F("UNKNOWN! (Error!)")); 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 index 77e8567..9f15b25 100644 --- 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 @@ -31,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: @@ -40,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" @@ -196,6 +193,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(); @@ -250,7 +249,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!")); } @@ -327,6 +326,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); // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- @@ -512,7 +513,7 @@ void printSocketParameters(int socket) Serial.println(F("Socket parameters:")); Serial.print(F("Socket type: ")); - int socketType; + SARA_R5_socket_protocol_t socketType; mySARA.querySocketType(socket, &socketType); if (socketType == SARA_R5_TCP) Serial.println(F("TCP")); @@ -543,29 +544,29 @@ void printSocketParameters(int socket) Serial.println(remoteAddress.toString()); Serial.print(F("Socket status (TCP sockets only): ")); - int socketStatus; + SARA_R5_tcp_socket_status_t socketStatus; mySARA.querySocketStatusTCP(socket, &socketStatus); - if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_INACTIVE) + if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_INACTIVE) Serial.println(F("INACTIVE")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_LISTEN) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_LISTEN) Serial.println(F("LISTEN")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_SYN_SENT) + 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) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_SYN_RCVD) Serial.println(F("SYN_RCVD")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_ESTABLISHED) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_ESTABLISHED) Serial.println(F("ESTABLISHED")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_FIN_WAIT_1) + 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) + 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) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_CLOSE_WAIT) Serial.println(F("CLOSE_WAIT")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_CLOSING) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_CLOSING) Serial.println(F("CLOSING")); - else if (socketStatus == SARA_R5::TCP_SOCKET_STATUS_LAST_ACK) + 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) + else if (socketStatus == SARA_R5_TCP_SOCKET_STATUS_TIME_WAIT) Serial.println(F("TIME_WAIT")); else Serial.println(F("UNKNOWN! (Error!)")); 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/keywords.txt b/keywords.txt index d1e71a2..c20918b 100644 --- a/keywords.txt +++ b/keywords.txt @@ -347,17 +347,17 @@ EXT_GNSS_TIMESTAMP LITERAL1 DTR_MODE LITERAL1 KHZ_32768_OUT LITERAL1 PAD_DISABLED LITERAL1 -TCP_SOCKET_STATUS_INACTIVE LITERAL1 -TCP_SOCKET_STATUS_LISTEN LITERAL1 -TCP_SOCKET_STATUS_SYN_SENT LITERAL1 -TCP_SOCKET_STATUS_SYN_RCVD LITERAL1 -TCP_SOCKET_STATUS_ESTABLISHED LITERAL1 -TCP_SOCKET_STATUS_FIN_WAIT_1 LITERAL1 -TCP_SOCKET_STATUS_FIN_WAIT_2 LITERAL1 -TCP_SOCKET_STATUS_CLOSE_WAIT LITERAL1 -TCP_SOCKET_STATUS_CLOSING LITERAL1 -TCP_SOCKET_STATUS_LAST_ACK LITERAL1 -TCP_SOCKET_STATUS_TIME_WAIT 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/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index d506103..c7aed06 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -38,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 @@ -90,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 - - int backlogLen = strlen(saraResponseBacklog); // Check how many bytes are in the backlog + memset(_saraRXBuffer, 0, _RXBuffSize); // Clear _saraRXBuffer - // 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)!")); } } @@ -168,7 +202,21 @@ bool SARA_R5::processURCEvent(const char *event) { 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; } } @@ -202,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")); @@ -306,7 +356,7 @@ bool SARA_R5::processURCEvent(const char *event) 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) { @@ -433,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 @@ -864,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; @@ -885,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; @@ -894,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; @@ -954,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) @@ -1019,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) { @@ -1082,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) { @@ -1146,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; } @@ -1185,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; @@ -1456,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; @@ -1686,7 +1757,6 @@ SARA_R5_error_t SARA_R5::getOperator(String *oper) _debugPort->print(F("getOperator: ")); _debugPort->println(*oper); } - //oper->concat('\0'); } } } @@ -1808,8 +1878,11 @@ 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) { @@ -2068,9 +2141,9 @@ 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(minimumResponseAllocation); if (response == NULL) @@ -2114,6 +2187,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); @@ -2187,7 +2261,6 @@ 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) @@ -2206,9 +2279,9 @@ SARA_R5_error_t SARA_R5::socketWrite(int socket, const char *str, int len) 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 (len == -1) { @@ -2324,7 +2397,7 @@ 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; + int scanNum = 0; int readLength = 0; int socketStore = 0; @@ -2341,7 +2414,7 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,%d", SARA_R5_READ_SOCKET, socket, length); - int responseLength = 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) { @@ -2355,8 +2428,10 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) if (err == SARA_R5_ERROR_SUCCESS) { // Extract the data - and check the quote is present - scanNum = sscanf(response, "\r\n+USORD: %d,%d,\"", - &socketStore, &readLength); + char *searchPtr = strstr(response, "+USORD: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USORD: %d,%d,\"", + &socketStore, &readLength); if (scanNum != 2) { if (_printDebug == true) @@ -2369,19 +2444,17 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } - // Check that readLength == length. + // 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: error: length=")); + _debugPort->print(F("socketRead: length mismatch! length=")); _debugPort->print(length); _debugPort->print(F(" readLength=")); _debugPort->println(readLength); } - free(command); - free(response); - return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } // Find the first double-quote: @@ -2395,7 +2468,7 @@ SARA_R5_error_t SARA_R5::socketRead(int socket, int length, char *readDest) } // Now copy the data into readDest - while (readIndex < length) + while (readIndex < readLength) { readDest[readIndex] = strBegin[1 + readIndex]; readIndex++; @@ -2416,7 +2489,7 @@ SARA_R5_error_t SARA_R5::socketReadAvailable(int socket, int *length) char *command; char *response; SARA_R5_error_t err; - int scanNum; + int scanNum = 0; int readLength = 0; int socketStore = 0; @@ -2437,8 +2510,10 @@ SARA_R5_error_t SARA_R5::socketReadAvailable(int socket, int *length) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "\r\n+USORD: %d,%d", - &socketStore, &readLength); + char *searchPtr = strstr(response, "+USORD: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USORD: %d,%d", + &socketStore, &readLength); if (scanNum != 2) { if (_printDebug == true) @@ -2467,7 +2542,7 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest, I char *strBegin; int readIndex = 0; SARA_R5_error_t err; - int scanNum; + int scanNum = 0; int remoteIPstore[4] = { 0, 0, 0, 0 }; int portStore = 0; int readLength = 0; @@ -2486,7 +2561,7 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest, I return SARA_R5_ERROR_OUT_OF_MEMORY; sprintf(command, "%s=%d,%d", SARA_R5_READ_UDP_SOCKET, socket, length); - int responseLength = 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) { @@ -2500,9 +2575,11 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest, I if (err == SARA_R5_ERROR_SUCCESS) { // Extract the data - and check the third quote is present - scanNum = sscanf(response, "\r\n+USORF: %d,\"%d.%d.%d.%d\",%d,%d,\"", - &socketStore, &remoteIPstore[0], &remoteIPstore[1], &remoteIPstore[2], &remoteIPstore[3], - &portStore, &readLength); + 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) @@ -2515,19 +2592,17 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest, I return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } - // Check that readLength == length. + // 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: error: length=")); + _debugPort->print(F("socketReadUDP: length mismatch! length=")); _debugPort->print(length); _debugPort->print(F(" readLength=")); _debugPort->println(readLength); } - free(command); - free(response); - return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } // Find the third double-quote @@ -2543,7 +2618,7 @@ SARA_R5_error_t SARA_R5::socketReadUDP(int socket, int length, char *readDest, I } // Now copy the data into readDest - while (readIndex < length) + while (readIndex < readLength) { readDest[readIndex] = strBegin[1 + readIndex]; readIndex++; @@ -2581,7 +2656,7 @@ SARA_R5_error_t SARA_R5::socketReadAvailableUDP(int socket, int *length) char *command; char *response; SARA_R5_error_t err; - int scanNum; + int scanNum = 0; int readLength = 0; int socketStore = 0; @@ -2602,8 +2677,10 @@ SARA_R5_error_t SARA_R5::socketReadAvailableUDP(int socket, int *length) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "\r\n+USORF: %d,%d", - &socketStore, &readLength); + char *searchPtr = strstr(response, "+UPSND: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+UPSND: %d,%d", + &socketStore, &readLength); if (scanNum != 2) { if (_printDebug == true) @@ -2743,12 +2820,12 @@ SARA_R5_error_t SARA_R5::socketDirectLinkCongestionTimer(int socket, unsigned lo return err; } -SARA_R5_error_t SARA_R5::querySocketType(int socket, int *protocol) +SARA_R5_error_t SARA_R5::querySocketType(int socket, SARA_R5_socket_protocol_t *protocol) { char *command; char *response; SARA_R5_error_t err; - int scanNum; + int scanNum = 0; int socketStore = 0; int paramVal; @@ -2769,8 +2846,10 @@ SARA_R5_error_t SARA_R5::querySocketType(int socket, int *protocol) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "\r\n+USOCTL: %d,0,%d", - &socketStore, ¶mVal); + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,0,%d", + &socketStore, ¶mVal); if (scanNum != 2) { if (_printDebug == true) @@ -2783,7 +2862,8 @@ SARA_R5_error_t SARA_R5::querySocketType(int socket, int *protocol) return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } - *protocol = paramVal; + *protocol = (SARA_R5_socket_protocol_t)paramVal; + _lastSocketProtocol[socketStore] = paramVal; } free(command); @@ -2797,7 +2877,7 @@ SARA_R5_error_t SARA_R5::querySocketLastError(int socket, int *error) char *command; char *response; SARA_R5_error_t err; - int scanNum; + int scanNum = 0; int socketStore = 0; int paramVal; @@ -2818,8 +2898,10 @@ SARA_R5_error_t SARA_R5::querySocketLastError(int socket, int *error) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "\r\n+USOCTL: %d,1,%d", - &socketStore, ¶mVal); + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,1,%d", + &socketStore, ¶mVal); if (scanNum != 2) { if (_printDebug == true) @@ -2846,7 +2928,7 @@ SARA_R5_error_t SARA_R5::querySocketTotalBytesSent(int socket, uint32_t *total) char *command; char *response; SARA_R5_error_t err; - int scanNum; + int scanNum = 0; int socketStore = 0; long unsigned int paramVal; @@ -2867,8 +2949,10 @@ SARA_R5_error_t SARA_R5::querySocketTotalBytesSent(int socket, uint32_t *total) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "\r\n+USOCTL: %d,2,%lu", - &socketStore, ¶mVal); + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,2,%lu", + &socketStore, ¶mVal); if (scanNum != 2) { if (_printDebug == true) @@ -2895,7 +2979,7 @@ SARA_R5_error_t SARA_R5::querySocketTotalBytesReceived(int socket, uint32_t *tot char *command; char *response; SARA_R5_error_t err; - int scanNum; + int scanNum = 0; int socketStore = 0; long unsigned int paramVal; @@ -2916,8 +3000,10 @@ SARA_R5_error_t SARA_R5::querySocketTotalBytesReceived(int socket, uint32_t *tot if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "\r\n+USOCTL: %d,3,%lu", - &socketStore, ¶mVal); + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,3,%lu", + &socketStore, ¶mVal); if (scanNum != 2) { if (_printDebug == true) @@ -2944,7 +3030,7 @@ SARA_R5_error_t SARA_R5::querySocketRemoteIPAddress(int socket, IPAddress *addre char *command; char *response; SARA_R5_error_t err; - int scanNum; + int scanNum = 0; int socketStore = 0; int paramVals[5]; @@ -2965,10 +3051,12 @@ SARA_R5_error_t SARA_R5::querySocketRemoteIPAddress(int socket, IPAddress *addre if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "\r\n+USOCTL: %d,4,\"%d.%d.%d.%d\",%d", - &socketStore, - ¶mVals[0], ¶mVals[1], ¶mVals[2], ¶mVals[3], - ¶mVals[4]); + 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) @@ -2993,12 +3081,12 @@ SARA_R5_error_t SARA_R5::querySocketRemoteIPAddress(int socket, IPAddress *addre return err; } -SARA_R5_error_t SARA_R5::querySocketStatusTCP(int socket, int *status) +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; + int scanNum = 0; int socketStore = 0; int paramVal; @@ -3019,8 +3107,10 @@ SARA_R5_error_t SARA_R5::querySocketStatusTCP(int socket, int *status) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "\r\n+USOCTL: %d,10,%d", - &socketStore, ¶mVal); + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,10,%d", + &socketStore, ¶mVal); if (scanNum != 2) { if (_printDebug == true) @@ -3033,7 +3123,7 @@ SARA_R5_error_t SARA_R5::querySocketStatusTCP(int socket, int *status) return SARA_R5_ERROR_UNEXPECTED_RESPONSE; } - *status = paramVal; + *status = (SARA_R5_tcp_socket_status_t)paramVal; } free(command); @@ -3047,7 +3137,7 @@ SARA_R5_error_t SARA_R5::querySocketOutUnackData(int socket, uint32_t *total) char *command; char *response; SARA_R5_error_t err; - int scanNum; + int scanNum = 0; int socketStore = 0; long unsigned int paramVal; @@ -3068,8 +3158,10 @@ SARA_R5_error_t SARA_R5::querySocketOutUnackData(int socket, uint32_t *total) if (err == SARA_R5_ERROR_SUCCESS) { - scanNum = sscanf(response, "\r\n+USOCTL: %d,11,%lu", - &socketStore, ¶mVal); + char *searchPtr = strstr(response, "+USOCTL: "); + if (searchPtr != NULL) + scanNum = sscanf(searchPtr, "+USOCTL: %d,11,%lu", + &socketStore, ¶mVal); if (scanNum != 2) { if (_printDebug == true) @@ -3117,7 +3209,9 @@ int SARA_R5::socketGetLastError() if (err == SARA_R5_ERROR_SUCCESS) { - sscanf(response, "\r\n+USOER: %d", &errorCode); + char *searchPtr = strstr(response, "+USOER: "); + if (searchPtr != NULL) + sscanf(searchPtr, "+USOER: %d", &errorCode); } free(command); @@ -3388,8 +3482,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; @@ -3546,7 +3644,7 @@ SARA_R5_error_t SARA_R5::getNetworkAssignedIPAddress(int profile, IPAddress *add scanNum = sscanf(searchPtr, "+UPSND: %d,%d,\"%d.%d.%d.%d\"", &profileStore, ¶mTag, ¶mVals[0], ¶mVals[1], ¶mVals[2], ¶mVals[3]); - if ((searchPtr == NULL) || (scanNum != 6)) + if (scanNum != 6) { if (_printDebug == true) { @@ -4191,10 +4289,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) { @@ -4219,8 +4320,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(); @@ -4258,11 +4358,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; + } } } @@ -4302,7 +4410,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())) @@ -4346,11 +4454,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; + } } } @@ -4382,21 +4498,27 @@ SARA_R5_error_t SARA_R5::sendCustomCommandWithResponse(const char *command, cons 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(); } } } @@ -4412,8 +4534,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) @@ -4735,27 +4855,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) @@ -4768,29 +4890,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 e9fe827..a70abfc 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -318,6 +318,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, @@ -363,9 +378,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 @@ -455,9 +467,6 @@ typedef enum class SARA_R5 : public Print { public: - char saraRXBuffer[RXBuffSize]; - char saraResponseBacklog[RXBuffSize]; - // 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. @@ -684,26 +693,12 @@ class SARA_R5 : public Print 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, int *protocol); + 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); - typedef enum - { - TCP_SOCKET_STATUS_INACTIVE, - TCP_SOCKET_STATUS_LISTEN, - TCP_SOCKET_STATUS_SYN_SENT, - TCP_SOCKET_STATUS_SYN_RCVD, - TCP_SOCKET_STATUS_ESTABLISHED, - TCP_SOCKET_STATUS_FIN_WAIT_1, - TCP_SOCKET_STATUS_FIN_WAIT_2, - TCP_SOCKET_STATUS_CLOSE_WAIT, - TCP_SOCKET_STATUS_CLOSING, - TCP_SOCKET_STATUS_LAST_ACK, - TCP_SOCKET_STATUS_TIME_WAIT - } SARA_R5_tcp_socket_status_t; - SARA_R5_error_t querySocketStatusTCP(int socket, int *status); + 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(); @@ -811,6 +806,13 @@ class SARA_R5 : public Print uint8_t _maxInitDepth; uint8_t _currentInitDepth = 0; + #define _RXBuffSize 2056 + const int _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 @@ -821,6 +823,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, @@ -846,7 +850,7 @@ class SARA_R5 : public Print 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); From 3f0e6f2058460999dae2332a9cb52b012bf8fe66 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Fri, 7 Jan 2022 21:16:36 +0000 Subject: [PATCH 15/17] Add deleteSMSmessage etc.. Update Example5 --- .../SARA-R5_Example5_ReceiveSMS.ino | 137 +++++++++++++++++- keywords.txt | 5 + ...parkFun_u-blox_SARA-R5_Arduino_Library.cpp | 21 ++- src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 7 + 4 files changed, 162 insertions(+), 8 deletions(-) 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 c20918b..a3d07c6 100644 --- a/keywords.txt +++ b/keywords.txt @@ -97,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 diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index c7aed06..9687997 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -1886,7 +1886,7 @@ SARA_R5_error_t SARA_R5::getPreferredMessageStorage(int *used, int *total, Strin { 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); @@ -2010,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; diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index a70abfc..661d294 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 @@ -599,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); From c56552323afbdcde5d1bbb4b32eb3bff82d136cc Mon Sep 17 00:00:00 2001 From: PaulZC Date: Fri, 7 Jan 2022 21:40:55 +0000 Subject: [PATCH 16/17] Fix compiler warning --- src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index 661d294..e9e8776 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -814,7 +814,7 @@ class SARA_R5 : public Print uint8_t _currentInitDepth = 0; #define _RXBuffSize 2056 - const int _rxWindowMillis = 2; // 1ms is not quite long enough for a single char at 9600 baud. millis roll over much less often than micros. + 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]; From fb0c78073d936b24d5e9ac872fc80901b874bab4 Mon Sep 17 00:00:00 2001 From: PaulZC Date: Fri, 7 Jan 2022 21:42:00 +0000 Subject: [PATCH 17/17] v1.0.5 --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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