From d3c4d1f657e49227850a6274de051bc5cd6b38af Mon Sep 17 00:00:00 2001 From: Sebastian Bergner <54766531+sebastianbergner@users.noreply.github.com> Date: Wed, 23 Aug 2023 09:52:04 +0200 Subject: [PATCH 1/7] update for use with UBLOX SARA-R5 modules Extend RegStatus, change getRegistrationStatus to check CREG --- src/TinyGsmClientUBLOX.h | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/TinyGsmClientUBLOX.h b/src/TinyGsmClientUBLOX.h index cf60040..bd97cf4 100644 --- a/src/TinyGsmClientUBLOX.h +++ b/src/TinyGsmClientUBLOX.h @@ -35,13 +35,18 @@ static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; #endif enum RegStatus { - REG_NO_RESULT = -1, - REG_UNREGISTERED = 0, - REG_SEARCHING = 2, - REG_DENIED = 3, - REG_OK_HOME = 1, - REG_OK_ROAMING = 5, - REG_UNKNOWN = 4, + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, + REG_SMS_ONLY_HOME = 6, + REG_SMS_ONLY_ROAMING = 7, + REG_EMERGENCY_ONLY = 8, // blox AT command manual (for SARA-R5 but should be applicable to all modules) (https://content.u-blox.com/sites/default/files/SARA-R5_ATCommands_UBX-19047455.pdf) states: attached for emergency bearer services only (see 3GPP TS 24.008 [85] and 3GPP TS 24.301 [120] that specify the condition when the MS is considered as attached for emergency bearer services) + REG_NO_FALLBACK_LTE_HOME = 9, // not 100% certain, ublox AT command manual states: registered for "CSFB not preferred", home network (applicable only when indicates E-UTRAN) + REG_NO_FALLBACK_LTE_ROAMING = 10 // not 100% certain, ublox AT command manual states: registered for "CSFB not preferred", roaming (applicable only when indicates E-UTRAN) }; class TinyGsmUBLOX : public TinyGsmModem, @@ -268,7 +273,8 @@ class TinyGsmUBLOX : public TinyGsmModem, */ public: RegStatus getRegistrationStatus() { - return (RegStatus)getRegistrationStatusXREG("CGREG"); + //use CREG instead of the CGREG AT command as it supports 2G/3G/4G instead of just 2G/3G connections + return (RegStatus)getRegistrationStatusXREG("CREG"); } bool setRadioAccessTecnology(int selected, int preferred) { @@ -297,7 +303,9 @@ class TinyGsmUBLOX : public TinyGsmModem, protected: bool isNetworkConnectedImpl() { RegStatus s = getRegistrationStatus(); - if (s == REG_OK_HOME || s == REG_OK_ROAMING) + // check for more registration staus, as some sim cards or tarifs only support SMS + // not all status are being checked, maybe have to be added + if (s == REG_OK_HOME || s == REG_OK_ROAMING || s == REG_SMS_ONLY_ROAMING || s == REG_SMS_ONLY_HOME) return true; else if (s == REG_UNKNOWN) // for some reason, it can hang at unknown.. return isGprsConnected(); From 617a70eaebd35c133d13983031a863a50764d36c Mon Sep 17 00:00:00 2001 From: Sebastian Bergner Date: Thu, 24 Aug 2023 16:38:00 +0200 Subject: [PATCH 2/7] revert changes to generic UBLOX class, create ublox SARA R5 classes, add required changes to TinyGsmClient --- src/TinyGsmClient.h | 6 + src/TinyGsmClientSaraR5.h | 893 ++++++++++++++++++++++++++++++++++++++ src/TinyGsmClientUBLOX.h | 26 +- 3 files changed, 908 insertions(+), 17 deletions(-) create mode 100644 src/TinyGsmClientSaraR5.h diff --git a/src/TinyGsmClient.h b/src/TinyGsmClient.h index a8ae763..64cb61d 100644 --- a/src/TinyGsmClient.h +++ b/src/TinyGsmClient.h @@ -68,6 +68,12 @@ typedef TinyGsmSaraR4 TinyGsm; typedef TinyGsmSaraR4::GsmClientSaraR4 TinyGsmClient; typedef TinyGsmSaraR4::GsmClientSecureR4 TinyGsmClientSecure; +#elif defined(TINY_GSM_MODEM_SARAR5) +#include "TinyGsmClientSaraR5.h" +typedef TinyGsmSaraR5 TinyGsm; +typedef TinyGsmSaraR5::GsmClientSaraR5 TinyGsmClient; +typedef TinyGsmSaraR5::GsmClientSecureR5 TinyGsmClientSecure; + #elif defined(TINY_GSM_MODEM_M95) #include "TinyGsmClientM95.h" typedef TinyGsmM95 TinyGsm; diff --git a/src/TinyGsmClientSaraR5.h b/src/TinyGsmClientSaraR5.h new file mode 100644 index 0000000..e9104ad --- /dev/null +++ b/src/TinyGsmClientSaraR5.h @@ -0,0 +1,893 @@ +/** + * @file TinyGsmClientSaraR5.h + * @author Sebastian Bergner, Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Aug 2023 + */ + +#ifndef SRC_TINYGSMCLIENTSARAR5_H_ +#define SRC_TINYGSMCLIENTSARAR5_H_ +// #pragma message("TinyGSM: TinyGsmClientSaraR5") + +// #define TINY_GSM_DEBUG Serial + +#define TINY_GSM_MUX_COUNT 7 +#define TINY_GSM_BUFFER_READ_AND_CHECK_SIZE + +#include "TinyGsmBattery.tpp" +#include "TinyGsmCalling.tpp" +#include "TinyGsmGPRS.tpp" +#include "TinyGsmGPS.tpp" +#include "TinyGsmGSMLocation.tpp" +#include "TinyGsmModem.tpp" +#include "TinyGsmSMS.tpp" +#include "TinyGsmSSL.tpp" +#include "TinyGsmTCP.tpp" +#include "TinyGsmTime.tpp" + +#include + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; +#if defined TINY_GSM_DEBUG +static const char GSM_CME_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CME ERROR:"; +static const char GSM_CMS_ERROR[] TINY_GSM_PROGMEM = GSM_NL "+CMS ERROR:"; +#endif + +enum RegStatus { + REG_NO_RESULT = -1, + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, + REG_SMS_ONLY_HOME = 6, + REG_SMS_ONLY_ROAMING = 7, + REG_EMERGENCY_ONLY = 8, // blox AT command manual states: attached for emergency bearer services only (see 3GPP TS 24.008 [85] and 3GPP TS 24.301 [120] that specify the condition when the MS is considered as attached for emergency bearer services) + REG_NO_FALLBACK_LTE_HOME = 9, // not 100% certain, ublox AT command manual states: registered for "CSFB not preferred", home network (applicable only when indicates E-UTRAN) + REG_NO_FALLBACK_LTE_ROAMING = 10 // not 100% certain, ublox AT command manual states: registered for "CSFB not preferred", roaming (applicable only when indicates E-UTRAN) +}; + +class TinyGsmSaraR5 : public TinyGsmModem, + public TinyGsmGPRS, + public TinyGsmTCP, + public TinyGsmSSL, + public TinyGsmCalling, + public TinyGsmSMS, + public TinyGsmGSMLocation, + public TinyGsmGPS, + public TinyGsmTime, + public TinyGsmBattery { + friend class TinyGsmModem; + friend class TinyGsmGPRS; + friend class TinyGsmTCP; + friend class TinyGsmSSL; + friend class TinyGsmCalling; + friend class TinyGsmSMS; + friend class TinyGsmGSMLocation; + friend class TinyGsmGPS; + friend class TinyGsmTime; + friend class TinyGsmBattery; + + /* + * Inner Client + */ + public: + class GsmClientSaraR5 : public GsmClient { + friend class TinyGsmSaraR5; + + public: + GsmClientSaraR5() {} + + explicit GsmClientSaraR5(TinyGsmSaraR5& modem, uint8_t mux = 0) { + init(&modem, mux); + } + + bool init(TinyGsmSaraR5* modem, uint8_t mux = 0) { + this->at = modem; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + if (mux < TINY_GSM_MUX_COUNT) { + this->mux = mux; + } else { + this->mux = (mux % TINY_GSM_MUX_COUNT); + } + at->sockets[this->mux] = this; + + return true; + } + + public: + virtual int connect(const char* host, uint16_t port, int timeout_s) { + // stop(); // DON'T stop! + TINY_GSM_YIELD(); + rx.clear(); + + uint8_t oldMux = mux; + sock_connected = at->modemConnect(host, port, &mux, false, timeout_s); + if (mux != oldMux) { + DBG("WARNING: Mux number changed from", oldMux, "to", mux); + at->sockets[oldMux] = NULL; + } + at->sockets[mux] = this; + at->maintain(); + + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + + void stop(uint32_t maxWaitMs) { + dumpModemBuffer(maxWaitMs); + at->sendAT(GF("+USOCL="), mux); + at->waitResponse(); // should return within 1s + sock_connected = false; + } + void stop() override { + stop(15000L); + } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + }; + + /* + * Inner Secure Client + */ + public: + class GsmClientSecureR5 : public GsmClientSaraR5 { + public: + GsmClientSecureR5() {} + + explicit GsmClientSecureR5(TinyGsmSaraR5& modem, uint8_t mux = 0) + : GsmClientSaraR5(modem, mux) {} + + public: + int connect(const char* host, uint16_t port, int timeout_s) override { + // stop(); // DON'T stop! + TINY_GSM_YIELD(); + rx.clear(); + uint8_t oldMux = mux; + sock_connected = at->modemConnect(host, port, &mux, true, timeout_s); + if (mux != oldMux) { + DBG("WARNING: Mux number changed from", oldMux, "to", mux); + at->sockets[oldMux] = NULL; + } + at->sockets[mux] = this; + at->maintain(); + return sock_connected; + } + TINY_GSM_CLIENT_CONNECT_OVERRIDES + }; + + /* + * Constructor + */ + public: + explicit TinyGsmSaraR5(Stream& stream) : stream(stream) { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + protected: + bool initImpl(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + DBG(GF("### TinyGSM Compiled Module: TinyGsmClientSaraR5")); + + if (!testAT()) { return false; } + + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + DBG(GF("### Modem:"), getModemName()); + + // Enable automatic time zome update + sendAT(GF("+CTZU=1")); + waitResponse(10000L); + // Ignore the response, in case the network doesn't support it. + // if (waitResponse(10000L) != 1) { return false; } + + SimStatus ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim + if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { + simUnlock(pin); + return (getSimStatus() == SIM_READY); + } else { + // if the sim is ready, or it's locked but no pin has been provided, + // return true + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + // only difference in implementation is the warning on the wrong type + String getModemNameImpl() { + sendAT(GF("+CGMI")); + String res1; + if (waitResponse(1000L, res1) != 1) { return "u-blox Cellular Modem"; } + res1.replace(GSM_NL "OK" GSM_NL, ""); + res1.trim(); + + sendAT(GF("+GMM")); + String res2; + if (waitResponse(1000L, res2) != 1) { return "u-blox Cellular Modem"; } + res2.replace(GSM_NL "OK" GSM_NL, ""); + res2.trim(); + + String name = res1 + String(' ') + res2; + if (name.startsWith("u-blox SARA-R4") || + name.startsWith("u-blox SARA-N4")) { + DBG("### WARNING: You are using the wrong TinyGSM modem!"); + } else if (name.startsWith("u-blox SARA-N2")) { + DBG("### SARA N2 NB-IoT modems not supported!"); + } + + return name; + } + + bool factoryDefaultImpl() { + sendAT(GF("+UFACTORY=0,1")); // No factory restore, erase NVM + waitResponse(); + return setPhoneFunctionality(16); // Reset + } + + /* + * Power functions + */ + protected: + bool restartImpl(const char* pin = NULL) { + if (!testAT()) { return false; } + if (!setPhoneFunctionality(16)) { return false; } + delay(3000); // TODO(?): Verify delay timing here + return init(pin); + } + + bool powerOffImpl() { + sendAT(GF("+CPWROFF")); + return waitResponse(40000L) == 1; + } + + bool sleepEnableImpl(bool enable = true) TINY_GSM_ATTR_NOT_AVAILABLE; + + bool setPhoneFunctionalityImpl(uint8_t fun, bool reset = false) { + sendAT(GF("+CFUN="), fun, reset ? ",1" : ""); + return waitResponse(10000L) == 1; + } + + /* + * Generic network functions + */ + public: + RegStatus getRegistrationStatus() { + //use CREG instead of the CGREG AT command as it supports 2G/3G/4G instead of just 2G/3G connections + return (RegStatus)getRegistrationStatusXREG("CREG"); + } + + bool setRadioAccessTechnology(int selected, int preferred) { + // selected: + // 0: GSM / GPRS / eGPRS (single mode) + // 1: GSM / UMTS (dual mode) + // 2: UMTS (single mode) + // 3: LTE (single mode) + // 4: GSM / UMTS / LTE (tri mode) + // 5: GSM / LTE (dual mode) + // 6: UMTS / LTE (dual mode) + // preferred: + // 0: GSM / GPRS / eGPRS + // 2: UTRAN + // 3: LTE + sendAT(GF("+URAT="), selected, GF(","), preferred); + if (waitResponse() != 1) { return false; } + return true; + } + + bool getCurrentRadioAccessTechnology(int&) { + // @TODO + return false; + } + + protected: + bool isNetworkConnectedImpl() { + RegStatus s = getRegistrationStatus(); + Serial.printf("REGISTRATION STATUS: %i\n", (int)s); + if (s == REG_OK_HOME || s == REG_OK_ROAMING || s == REG_SMS_ONLY_ROAMING || s == REG_SMS_ONLY_HOME) + return true; + else if (s == REG_UNKNOWN) // for some reason, it can hang at unknown.. + return isGprsConnected(); + else + return false; + } + + String getLocalIPImpl() { + sendAT(GF("+UPSND=0,0")); + if (waitResponse(GF(GSM_NL "+UPSND:")) != 1) { return ""; } + streamSkipUntil(','); // Skip PSD profile + streamSkipUntil('\"'); // Skip request type + String res = stream.readStringUntil('\"'); + if (waitResponse() != 1) { return ""; } + return res; + } + + /* + * GPRS functions + */ + protected: + bool gprsConnectImpl(const char* apn, const char* user = NULL, + const char* pwd = NULL) { + + sendAT(GF("+CGATT=1")); // attach to GPRS + if (waitResponse(360000L) != 1) { return false; } + + // Setting up the PSD profile/PDP context with the UPSD commands sets up an + // "internal" PDP context, i.e. a data connection using the internal IP + // stack and related AT commands for sockets. + + // Packet switched data configuration + // AT+UPSD=,, + // profile_id = 0 - PSD profile identifier, in range 0-6 (NOT PDP context) + // param_tag = 1: APN + // param_tag = 2: username -> not working for SARA-R5 + // param_tag = 3: password -> not working for SARA-R5 + // param_tag = 7: IP address Note: IP address set as "0.0.0.0" means + // dynamic IP address assigned during PDP context activation + + + // check all available PDP context identifiers + String response; + response.reserve(1024); + sendAT(GF("+CGDCONT?")); + //Serial.println(waitResponse(300, response, NULL));// overwrite GSM_OK as the stream most probably still has data which would cause ### Unhandled: .... + waitResponseUntilEndStream(1000, response); + Serial.println(response); + + if (response.length() == 0){ + return false; // no apn at all found + } + // Serial.println("we got here 3"); + // parse string & look for apn -> modified from SparkFun u-blox SARA-R5 lib + // Example: + // +CGDCONT: 0,"IP","payandgo.o2.co.uk","0.0.0.0",0,0,0,0,0,0,0,0,0,0 + // +CGDCONT: 1,"IP","payandgo.o2.co.uk.mnc010.mcc234.gprs","10.160.182.234",0,0,0,2,0,0,0,0,0,0 + + //create search buffer where we can search + char *searchBuf = (char*)malloc(response.length()+1); + response.toCharArray(searchBuf, response.length()+1); + + int rcid = -1; + char *searchPtr = searchBuf; + + for(size_t index = 0; index<=response.length(); index++){ + int scanned = 0; + // Find the first/next occurrence of +CGDCONT: + searchPtr = strstr(searchPtr, "+CGDCONT:"); + if (searchPtr != nullptr){ + char strPdpType[10]; + char strApn[128]; + int ipOct[4]; + + searchPtr += strlen("+CGDCONT:"); + while (*searchPtr == ' ') searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%d,\"%[^\"]\",\"%[^\"]\",\"%d.%d.%d.%d", &rcid, strPdpType, strApn, &ipOct[0], &ipOct[1], &ipOct[2], &ipOct[3]); + + if (!strcmp(strApn, apn)){ + // found the configuration that we want to connect to + break; + } + } + } + + sendAT(GF("+UPSDA=0,4")); // Deactivate the PDP context associated with profile 0 + waitResponse(360000L); // Can return an error if previously not activated + + sendAT(GF("+UPSD=0,100,")+String(rcid)); // Deactivate the PDP context associated with profile 0 + waitResponse(); + + sendAT(GF("+UPSDA=0,3")); // Activate the PDP context associated with profile 0 + if (waitResponse(360000L) != 1) { return false; } + + sendAT(GF("+UPSD=0,0,2")); // Set protocol type to IPv4v6 with IPv4 preferred for internal sockets + waitResponse(); + + return true; + } + + bool gprsDisconnectImpl() { + sendAT(GF( + "+UPSDA=0,4")); // Deactivate the PDP context associated with profile 0 + if (waitResponse(360000L) != 1) { return false; } + + sendAT(GF("+CGATT=0")); // detach from GPRS + if (waitResponse(360000L) != 1) { return false; } + + return true; + } + + /* + * SIM card functions + */ + protected: + // This uses "CGSN" instead of "GSN" + String getIMEIImpl() { + sendAT(GF("+CGSN")); + if (waitResponse(GF(GSM_NL)) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * Phone Call functions + */ + protected: + // Can follow all of the phone call functions from the template + + /* + * Messaging functions + */ + protected: + // Can follow all template functions + + /* + * GSM/GPS/GNSS/GLONASS Location functions + * NOTE: u-blox modules use the same function to get location data from both + * GSM tower triangulation and from dedicated GPS/GNSS/GLONASS receivers. The + * only difference in which sensor the data is requested from. If a GNSS + * location is requested from a modem without a GNSS receiver installed on the + * I2C port, the GSM-based "Cell Locate" location will be returned instead. + */ + protected: + bool enableGPSImpl() { + // AT+UGPS=[,[,]] + // - 0: GNSS receiver powered off, 1: on + // - 0: no aiding (default) + // - 3: GPS + SBAS (default) + sendAT(GF("+UGPS=1,0,3")); + if (waitResponse(10000L, GF(GSM_NL "+UGPS:")) != 1) { return false; } + return waitResponse(10000L) == 1; + } + bool disableGPSImpl() { + sendAT(GF("+UGPS=0")); + if (waitResponse(10000L, GF(GSM_NL "+UGPS:")) != 1) { return false; } + return waitResponse(10000L) == 1; + } + String inline getUbloxLocationRaw(int8_t sensor) { + // AT+ULOC=,,,, + // - 2: single shot position + // - 0: use the last fix in the internal database and stop the GNSS + // receiver + // - 1: use the GNSS receiver for localization + // - 2: use cellular CellLocate location information + // - 3: ?? use the combined GNSS receiver and CellLocate service + // information ?? - Docs show using sensor 3 and it's + // documented for the +UTIME command but not for +ULOC + // - 0: standard (single-hypothesis) response + // - Timeout period in seconds + // - Target accuracy in meters (1 - 999999) + sendAT(GF("+ULOC=2,"), sensor, GF(",0,120,1")); + // wait for first "OK" + if (waitResponse(10000L) != 1) { return ""; } + // wait for the final result - wait full timeout time + if (waitResponse(120000L, GF(GSM_NL "+UULOC:")) != 1) { return ""; } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + String getGsmLocationRawImpl() { + return getUbloxLocationRaw(2); + } + String getGPSrawImpl() { + return getUbloxLocationRaw(1); + } + + inline bool getUbloxLocation(int8_t sensor, float* lat, float* lon, + float* speed = 0, float* alt = 0, int* vsat = 0, + int* usat = 0, float* accuracy = 0, + int* year = 0, int* month = 0, int* day = 0, + int* hour = 0, int* minute = 0, + int* second = 0) { + // AT+ULOC=,,,, + // - 2: single shot position + // - 2: use cellular CellLocate location information + // - 0: use the last fix in the internal database and stop the GNSS + // receiver + // - 1: use the GNSS receiver for localization + // - 3: ?? use the combined GNSS receiver and CellLocate service + // information ?? - Docs show using sensor 3 and it's documented + // for the +UTIME command but not for +ULOC + // - 0: standard (single-hypothesis) response + // - Timeout period in seconds + // - Target accuracy in meters (1 - 999999) + sendAT(GF("+ULOC=2,"), sensor, GF(",0,120,1")); + // wait for first "OK" + if (waitResponse(10000L) != 1) { return false; } + // wait for the final result - wait full timeout time + if (waitResponse(120000L, GF(GSM_NL "+UULOC: ")) != 1) { return false; } + + // +UULOC: ,