diff --git a/examples/Audio_Examples/AudioExample1_PlayTone/AudioExample1_PlayTone.ino b/examples/Audio_Examples/AudioExample1_PlayTone/AudioExample1_PlayTone.ino new file mode 100644 index 0000000..88b093d --- /dev/null +++ b/examples/Audio_Examples/AudioExample1_PlayTone/AudioExample1_PlayTone.ino @@ -0,0 +1,145 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular_Voice myModule; // This example works with all voice-enabled modules, so this base class can be used +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; + +void setup() +{ + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Audio Example 1 - Play Tone")); + + Serial.println(); + Serial.println(F("! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! !")); + Serial.println(F("This example requires an audio codec attached to the I2S interface")); + Serial.println(F("of the cellular modem. Please add one and update this example as")); + Serial.println(F("needed to configure your audio codec!")); + Serial.println(F("! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! !")); + Serial.println(); + + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); +} + +void loop() +{ + String inputString; + char dtmfChar = 0; + uint16_t frequency = 0; + uint16_t duration = 0; + uint8_t volume = 0; + + while (true) + { + while (Serial.available() != 0) + { + Serial.read(); + } + Serial.println(F("Enter a frequency in Hz (300-3400) or a DTMF character (0-9, *, #)")); + while (Serial.available() == 0) + { + } + + inputString = Serial.readStringUntil('\n'); + + if (inputString.length() == 1) + { + dtmfChar = inputString.charAt(0); + if ((dtmfChar >= '0' && dtmfChar <= '9') || dtmfChar == '*' || dtmfChar == '#') + { + break; + } + } + else + { + frequency = inputString.toInt(); + if (frequency >= 300 && frequency <= 3400) + { + dtmfChar == 0; + break; + } + } + } + + while (true) + { + while (Serial.available() != 0) + { + Serial.read(); + } + Serial.println(F("Enter a duration in ms (50-1360)")); + while (Serial.available() == 0) + { + } + + inputString = Serial.readStringUntil('\n'); + duration = inputString.toInt(); + if (duration >= 50 && duration <= 1360) + { + break; + } + } + + while (true) + { + while (Serial.available() != 0) + { + Serial.read(); + } + Serial.println(F("Enter a volume (0-100)")); + while (Serial.available() == 0) + { + } + + inputString = Serial.readStringUntil('\n'); + volume = inputString.toInt(); + if (volume <= 100) + { + break; + } + } + + if (dtmfChar == 0) + { + myModule.generateToneFreq(frequency, duration, volume); + } + else + { + myModule.generateToneDTMF(dtmfChar, duration, volume); + } +} \ No newline at end of file diff --git a/examples/Audio_Examples/AudioExample2_Loopback/AudioExample2_Loopback.ino b/examples/Audio_Examples/AudioExample2_Loopback/AudioExample2_Loopback.ino new file mode 100644 index 0000000..3adafab --- /dev/null +++ b/examples/Audio_Examples/AudioExample2_Loopback/AudioExample2_Loopback.ino @@ -0,0 +1,82 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular_Voice myModule; // This example works with all voice-enabled modules, so this base class can be used +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; + +void setup() +{ + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Audio Example 2 - Loopback")); + + Serial.println(); + Serial.println(F("! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! !")); + Serial.println(F("This example requires an audio codec attached to the I2S interface")); + Serial.println(F("of the cellular modem. Please add one and update this example as")); + Serial.println(F("needed to configure your audio codec!")); + Serial.println(F("! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! !")); + Serial.println(); + + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); +} + +void loop() +{ + while (Serial.available() != 0) + { + Serial.read(); + } + Serial.println(F("Enter any key to begin loopback")); + while (Serial.available() == 0) + { + } + + myModule.playAudioResource(UBX_CELL_AUDIO_RESOURCE_LOOPBACK); + + while (Serial.available() != 0) + { + Serial.read(); + } + Serial.println(F("Enter any key to stop loopback")); + while (Serial.available() == 0) + { + } + + myModule.stopAudioResource(UBX_CELL_AUDIO_RESOURCE_LOOPBACK); +} \ No newline at end of file diff --git a/examples/Audio_Examples/AudioExample3_CallControl/AudioExample3_CallControl.ino b/examples/Audio_Examples/AudioExample3_CallControl/AudioExample3_CallControl.ino new file mode 100644 index 0000000..cf6a29a --- /dev/null +++ b/examples/Audio_Examples/AudioExample3_CallControl/AudioExample3_CallControl.ino @@ -0,0 +1,137 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular_Voice myModule; // This example works with all voice-enabled modules, so this base class can be used +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; + +bool callInProgress = false; +bool incomingCall = false; + +void ringCallback() +{ + Serial.println(F("Incoming call! Enter \"A\" to answer, or anything else to reject")); + incomingCall = true; +} + +void setup() +{ + String currentOperator = ""; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Audio Example 3 - Call Control")); + + Serial.println(); + Serial.println(F("! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! !")); + Serial.println(F("This example requires an audio codec attached to the I2S interface")); + Serial.println(F("of the cellular modem. Please add one and update this example as")); + Serial.println(F("needed to configure your audio codec!")); + Serial.println(F("! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! ! ATTENTION ! ! ! ! !")); + Serial.println(); + + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); + + // First check to see if we're connected to an operator: + if (myModule.getOperator(¤tOperator) == UBX_CELL_SUCCESS) + { + Serial.print(F("Connected to: ")); + Serial.println(currentOperator); + } + else + { + Serial.print(F("The module is not yet connected to an operator. Please use the previous examples to connect. " + "Or wait and retry. Freezing...")); + while (1) + ; // Do nothing more + } + + // Set callback function for when a new call is received + myModule.setRingCallback(&ringCallback); + + Serial.println(F("Enter a number to dial")); + + // Clear any input + while (Serial.available()) + { + Serial.read(); + } +} + +void loop() +{ + String inputString; + + myModule.bufferedPoll(); + + if (Serial.available()) + { + inputString = Serial.readStringUntil('\n'); + while (Serial.available()) + { + Serial.read(); + } + + if (incomingCall) + { + if (inputString == "A" || inputString == "a") + { + Serial.println(F("Answering call, enter any key to hang up")); + myModule.answer(); + callInProgress = true; + } + else + { + Serial.println(F("Rejecting call")); + myModule.hangUp(); + } + incomingCall = false; + } + else if (callInProgress == false) + { + Serial.println("Dialing " + inputString + ", enter any key to hang up"); + myModule.dial(inputString); + callInProgress = true; + } + else + { + Serial.println(F("Hanging up, enter a new number to dial")); + myModule.hangUp(); + callInProgress = false; + } + } +} \ No newline at end of file diff --git a/examples/Example1_DeviceIdentification/Example1_DeviceIdentification.ino b/examples/Example1_DeviceIdentification/Example1_DeviceIdentification.ino new file mode 100644 index 0000000..47089c1 --- /dev/null +++ b/examples/Example1_DeviceIdentification/Example1_DeviceIdentification.ino @@ -0,0 +1,101 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular myModule; // This example works with all modules, so the base class can be used +// SparkFun_ublox_SARA_R5 myModule; // Base SARA-R5 class +// SparkFun_ublox_SARA_R500S myModule; +// SparkFun_ublox_SARA_R500S_01B myModule; +// SparkFun_ublox_SARA_R500S_61B myModule; +// SparkFun_ublox_SARA_R510M8S_61B myModule; +// SparkFun_ublox_SARA_R510S myModule; +// SparkFun_ublox_LARA_R6 myModule; // Base LARA-R6 class +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6001D myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6401D myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; +// SparkFun_ublox_LARA_R6801D myModule; + +// Map SIM states to more readable strings +String simStateString[] = { + "Not present", // 0 + "PIN needed", // 1 + "PIN blocked", // 2 + "PUK blocked", // 3 + "Not operational", // 4 + "Restricted", // 5 + "Operational" // 6 +}; + +// processSIMstate is provided to the u-blox cellular library via a +// callback setter -- setSIMstateReadCallback. (See setup()) +void processSIMstate(UBX_CELL_sim_states_t state) +{ + Serial.println(); + Serial.print(F("SIM state: ")); + Serial.print(String(state)); + Serial.println(); +} + +void setup() +{ + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Example 1 - Device Identification")); + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); + + Serial.println("Manufacturer ID: " + String(myModule.getManufacturerID())); + Serial.println("Model ID: " + String(myModule.getModelID())); + Serial.println("Firmware Version: " + String(myModule.getFirmwareVersion())); + Serial.println("Product Serial No.: " + String(myModule.getSerialNo())); + Serial.println("IMEI: " + String(myModule.getIMEI())); + Serial.println("IMSI: " + String(myModule.getIMSI())); + Serial.println("SIM CCID: " + String(myModule.getCCID())); + Serial.println("Subscriber No.: " + String(myModule.getSubscriberNo())); + Serial.println("Capabilities: " + String(myModule.getCapabilities())); + + // Set a callback to return the SIM state once requested + myModule.setSIMstateReportCallback(&processSIMstate); + // Now enable SIM state reporting for states 0 to 6 (by setting the reporting mode LSb) + if (myModule.setSIMstateReportingMode(1) == UBX_CELL_SUCCESS) + Serial.println("SIM state reports requested..."); + // You can disable the SIM staus reports again by calling assetTracker.setSIMstateReportingMode(0) +} + +void loop() +{ + myModule.poll(); // Keep processing data from the module so we can extract the SIM status +} \ No newline at end of file diff --git a/examples/Example2_NetworkInfo/Example2_NetworkInfo.ino b/examples/Example2_NetworkInfo/Example2_NetworkInfo.ino new file mode 100644 index 0000000..3a851ba --- /dev/null +++ b/examples/Example2_NetworkInfo/Example2_NetworkInfo.ino @@ -0,0 +1,124 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular myModule; // This example works with all modules, so the base class can be used +// SparkFun_ublox_SARA_R5 myModule; // Base SARA-R5 class +// SparkFun_ublox_SARA_R500S myModule; +// SparkFun_ublox_SARA_R500S_01B myModule; +// SparkFun_ublox_SARA_R500S_61B myModule; +// SparkFun_ublox_SARA_R510M8S_61B myModule; +// SparkFun_ublox_SARA_R510S myModule; +// SparkFun_ublox_LARA_R6 myModule; // Base LARA-R6 class +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6001D myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6401D myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; +// SparkFun_ublox_LARA_R6801D myModule; + +// Map registration status messages to more readable strings +String registrationString[] = { + "Not registered", // 0 + "Registered, home", // 1 + "Searching for operator", // 2 + "Registration denied", // 3 + "Registration unknown", // 4 + "Registered, roaming", // 5 + "Registered, home (SMS only)", // 6 + "Registered, roaming (SMS only)", // 7 + "Registered, emergency service only", // 8 + "Registered, home, CSFB not preferred", // 9 + "Registered, roaming, CSFB not prefered" // 10 +}; + +// If you are based in Europe, you will (probably) need to select MNO_STD_EUROPE +const mobile_network_operator_t MOBILE_NETWORK_OPERATOR = MNO_GLOBAL; + +void setup() +{ + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Example 2 - Network Info")); + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); + + if (!myModule.setNetworkProfile(MOBILE_NETWORK_OPERATOR)) + { + Serial.println(F("Error setting network. Try cycling the power.")); + while (1) + ; + } + + Serial.println(F("Network profile set. Ready to go!")); + + // RSSI: Received signal strength: + Serial.println("RSSI: " + String(myModule.rssi())); + // Registration Status + int regStatus = myModule.registration(); + if ((regStatus >= 0) && (regStatus <= 10)) + { + Serial.println("Network registration: " + registrationString[regStatus]); + } + + // Print the Context IDs, Access Point Names and IP Addresses + Serial.println(F("Available PDP (Packet Data Protocol) APNs (Access Point Names) and IP Addresses:")); + Serial.println(F("Context ID:\tAPN Name:\tIP Address:")); + for (int cid = 0; cid < UBX_CELL_NUM_PDP_CONTEXT_IDENTIFIERS; cid++) + { + String apn = ""; + IPAddress ip(0, 0, 0, 0); + myModule.getAPN(cid, &apn, &ip); + if (apn.length() > 0) + { + Serial.print(cid); + Serial.print(F("\t\t")); + Serial.print(apn); + Serial.print(F("\t")); + Serial.println(ip); + } + } + + Serial.println(); + + if (regStatus > 0) + { + Serial.println(F("All set. Go to the next example!")); + } +} + +void loop() +{ + // Do nothing. Now that we're registered move on to the next example. +} \ No newline at end of file diff --git a/examples/Example3_RegisterOperator/Example3_RegisterOperator.ino b/examples/Example3_RegisterOperator/Example3_RegisterOperator.ino new file mode 100644 index 0000000..5a459bc --- /dev/null +++ b/examples/Example3_RegisterOperator/Example3_RegisterOperator.ino @@ -0,0 +1,359 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular myModule; // This example works with all modules, so the base class can be used +// SparkFun_ublox_SARA_R5 myModule; // Base SARA-R5 class +// SparkFun_ublox_SARA_R500S myModule; +// SparkFun_ublox_SARA_R500S_01B myModule; +// SparkFun_ublox_SARA_R500S_61B myModule; +// SparkFun_ublox_SARA_R510M8S_61B myModule; +// SparkFun_ublox_SARA_R510S myModule; +// SparkFun_ublox_LARA_R6 myModule; // Base LARA-R6 class +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6001D myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6401D myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; +// SparkFun_ublox_LARA_R6801D myModule; + +// Map registration status messages to more readable strings +String registrationString[] = { + "Not registered", // 0 + "Registered, home", // 1 + "Searching for operator", // 2 + "Registration denied", // 3 + "Registration unknown", // 4 + "Registered, roaming", // 5 + "Registered, home (SMS only)", // 6 + "Registered, roaming (SMS only)", // 7 + "Registered, emergency service only", // 8 + "Registered, home, CSFB not preferred", // 9 + "Registered, roaming, CSFB not prefered" // 10 +}; + +// If you are based in Europe, you will (probably) need to select MNO_STD_EUROPE +const mobile_network_operator_t MOBILE_NETWORK_OPERATOR = MNO_GLOBAL; + +const String MOBILE_NETWORK_STRINGS[] = {"default (Undefined/Regulatory)", + "SIM ICCID", + "AT&T", + "Verizon", + "Telstra", + "T-Mobile US", + "China Telecom", + "Sprint", + "Vodafone", + "NTT DoCoMo", + "Telus", + "SoftBank", + "Deutsche Telekom", + "US Cellular", + "SKT", + "global (factory default)", + "standard Europe", + "standard Europe No-ePCO", + "NOT RECOGNIZED"}; + +// Convert the operator number into an index for MOBILE_NETWORK_STRINGS +int convertOperatorNumber(mobile_network_operator_t mno) +{ + switch (mno) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + return ((int)mno); + case 8: + return 7; + case 19: + return 8; + case 20: + return 9; + case 21: + return 10; + case 28: + return 11; + case 31: + return 12; + case 32: + return 13; + case 39: + return 14; + case 90: + return 15; + case 100: + return 16; + case 101: + return 17; + default: // NOT RECOGNIZED + return 18; + } +} + +// This defines the size of the ops struct array. To narrow the operator +// list, set MOBILE_NETWORK_OPERATOR to AT&T, Verizon etc. instead +// of MNO_SW_DEFAULT. +#define MAX_OPERATORS 10 + +// Uncomment this line if you want to be able to communicate directly with the module in the main loop +// #define DEBUG_PASSTHROUGH_ENABLED + +void setup() +{ + int opsAvailable; + struct operator_stats ops[MAX_OPERATORS]; + String currentOperator = ""; + bool newConnection = true; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Example 3 - Register Operator")); + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); + + // First check to see if we're already connected to an operator: + if (myModule.getOperator(¤tOperator) == UBX_CELL_SUCCESS) + { + Serial.print(F("Already connected to: ")); + Serial.println(currentOperator); + // If already connected provide the option to type y to connect to new operator + Serial.println(F("Press y to connect to a new operator, or any other key to continue.\r\n")); + while (!Serial.available()) + ; + if (Serial.read() != 'y') + { + newConnection = false; + } + else + { + myModule.deregisterOperator(); // Deregister from the current operator so we can connect to a new one + } + while (Serial.available()) + Serial.read(); + } + + if (newConnection) + { + // Set MNO to either Verizon, T-Mobile, AT&T, Telstra, etc. + // This will narrow the operator options during our scan later + Serial.println(F("Setting mobile-network operator")); + if (myModule.setNetworkProfile(MOBILE_NETWORK_OPERATOR)) + { + Serial.print(F("Set mobile network operator to ")); + Serial.println(MOBILE_NETWORK_STRINGS[convertOperatorNumber(MOBILE_NETWORK_OPERATOR)] + "\r\n"); + } + else + { + Serial.println(F("Error setting MNO. Try cycling the power. Freezing...")); + while (1) + ; + } + + // Wait for user to press button before initiating network scan. + Serial.println(F("Press any key scan for networks..")); + serialWait(); + + Serial.println(F("Scanning for networks...this may take up to 3 minutes\r\n")); + // myModule.getOperators takes in a operator_stats struct pointer and max number of + // structs to scan for, then fills up those objects with operator names and numbers + opsAvailable = myModule.getOperators(ops, MAX_OPERATORS); // This will block for up to 3 minutes + + if (opsAvailable > 0) + { + // Pretty-print operators we found: + Serial.println("Found " + String(opsAvailable) + " operators:"); + printOperators(ops, opsAvailable); + Serial.println(String(opsAvailable + 1) + ": use automatic selection"); + Serial.println(); + + // Wait until the user presses a key to initiate an operator connection + Serial.println("Press 1-" + String(opsAvailable + 1) + " to select an operator."); + char c = 0; + bool selected = false; + while (!selected) + { + while (!Serial.available()) + ; + c = Serial.read(); + int selection = c - '0'; + if ((selection >= 1) && (selection <= (opsAvailable + 1))) + { + selected = true; + Serial.println("Connecting to option " + String(selection)); + if (selection == (opsAvailable + 1)) + { + if (myModule.automaticOperatorSelection() == UBX_CELL_SUCCESS) + { + Serial.println("Automatic operator selection: successful\r\n"); + } + else + { + Serial.println( + F("Automatic operator selection: error. Reset and try again, or try another network.")); + } + } + else + { + if (myModule.registerOperator(ops[selection - 1]) == UBX_CELL_SUCCESS) + { + Serial.println("Network " + ops[selection - 1].longOp + " registered\r\n"); + } + else + { + Serial.println( + F("Error connecting to operator. Reset and try again, or try another network.")); + } + } + } + } + } + else + { + Serial.println(F("Did not find an operator. Double-check SIM and antenna, reset and try again, or try " + "another network.")); + while (1) + ; + } + } + + // At the very end print connection information + printInfo(); +} + +void loop() +{ + // Loop provides a debugging interface. + if (mySerial.available()) + { + Serial.write((char)mySerial.read()); + } +#ifdef DEBUG_PASSTHROUGH_ENABLED + if (Serial.available()) + { + mySerial.write((char)Serial.read()); + } +#endif +} + +void printInfo(void) +{ + String currentApn = ""; + IPAddress ip(0, 0, 0, 0); + String currentOperator = ""; + + Serial.println(F("Connection info:")); + Serial.println(F("Context ID:\tAPN Name:\tIP Address:")); + for (int cid = 0; cid < UBX_CELL_NUM_PDP_CONTEXT_IDENTIFIERS; cid++) + { + String apn = ""; + IPAddress ip(0, 0, 0, 0); + myModule.getAPN(cid, &apn, &ip); + if (apn.length() > 0) + { + Serial.print(cid); + Serial.print(F("\t\t")); + Serial.print(apn); + Serial.print(F("\t")); + Serial.println(ip); + } + } + + // Operator name or number + if (myModule.getOperator(¤tOperator) == UBX_CELL_SUCCESS) + { + Serial.print(F("Operator: ")); + Serial.println(currentOperator); + } + + // Received signal strength + Serial.println("RSSI: " + String(myModule.rssi())); + Serial.println(); +} + +void printOperators(struct operator_stats *ops, int operatorsAvailable) +{ + for (int i = 0; i < operatorsAvailable; i++) + { + Serial.print(String(i + 1) + ": "); + Serial.print(ops[i].longOp + " (" + String(ops[i].numOp) + ") - "); + switch (ops[i].stat) + { + case 0: + Serial.print(F("UNKNOWN")); + break; + case 1: + Serial.print(F("AVAILABLE")); + break; + case 2: + Serial.print(F("CURRENT")); + break; + case 3: + Serial.print(F("FORBIDDEN")); + break; + } + switch (ops[i].act) + { + case 0: + Serial.print(F(" - GSM")); + break; + case 2: + Serial.print(F(" - UTRAN")); + break; + case 3: + Serial.print(F(" - GSM/GPRS with EDGE")); + break; + case 7: + Serial.print(F(" - LTE")); // SARA-R5 only supports LTE + break; + } + Serial.println(); + } + Serial.println(); +} + +void serialWait() +{ + while (Serial.available()) + Serial.read(); + while (!Serial.available()) + ; + delay(100); + while (Serial.available()) + Serial.read(); +} \ No newline at end of file diff --git a/examples/Example4_Clock/Example4_Clock.ino b/examples/Example4_Clock/Example4_Clock.ino new file mode 100644 index 0000000..774f92c --- /dev/null +++ b/examples/Example4_Clock/Example4_Clock.ino @@ -0,0 +1,82 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular myModule; // This example works with all modules, so the base class can be used +// SparkFun_ublox_SARA_R5 myModule; // Base SARA-R5 class +// SparkFun_ublox_SARA_R500S myModule; +// SparkFun_ublox_SARA_R500S_01B myModule; +// SparkFun_ublox_SARA_R500S_61B myModule; +// SparkFun_ublox_SARA_R510M8S_61B myModule; +// SparkFun_ublox_SARA_R510S myModule; +// SparkFun_ublox_LARA_R6 myModule; // Base LARA-R6 class +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6001D myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6401D myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; +// SparkFun_ublox_LARA_R6801D myModule; + +void setup() +{ + String currentOperator = ""; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Example 4 - Clock")); + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); + + // Make sure automatic time zone updates are enabled + if (myModule.autoTimeZone(true) != UBX_CELL_SUCCESS) + Serial.println(F("Enable autoTimeZone failed!")); + + // Read and print the clock as a String + String theTime = myModule.clock(); + Serial.println(theTime); + + // Read and print the hour, minute, etc. separately + uint8_t year, month, day, hour, minute, second; + int8_t timeZone; + if (myModule.clock(&year, &month, &day, &hour, &minute, &second, &timeZone) == UBX_CELL_SUCCESS) + // Note: not all Arduino boards implement printf correctly. The formatting may not be correct on some boards. + // Note: the timeZone is defined in 15 minute increments, not hours. -28 indicates the time zone is 7 hours + // behind UTC/GMT. + Serial.printf("%02d/%02d/%02d %02d:%02d:%02d %+d\r\n", year, month, day, hour, minute, second, timeZone); +} + +void loop() +{ + // Nothing to do here +} \ No newline at end of file diff --git a/examples/Example5_Ping/Example5_Ping.ino b/examples/Example5_Ping/Example5_Ping.ino new file mode 100644 index 0000000..8682cd5 --- /dev/null +++ b/examples/Example5_Ping/Example5_Ping.ino @@ -0,0 +1,142 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular myModule; // This example works with all modules, so the base class can be used +// SparkFun_ublox_SARA_R5 myModule; // Base SARA-R5 class +// SparkFun_ublox_SARA_R500S myModule; +// SparkFun_ublox_SARA_R500S_01B myModule; +// SparkFun_ublox_SARA_R500S_61B myModule; +// SparkFun_ublox_SARA_R510M8S_61B myModule; +// SparkFun_ublox_SARA_R510S myModule; +// SparkFun_ublox_LARA_R6 myModule; // Base LARA-R6 class +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6001D myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6401D myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; +// SparkFun_ublox_LARA_R6801D myModule; + +String pingMe = ""; // The name of the server we are going to ping + +// processPingResult is provided to the u-blox cellular library via a +// callback setter -- setPingCallback. (See the end of setup()) +void processPingResult(int retry, int p_size, String remote_hostname, IPAddress ip, int ttl, long rtt) +{ + Serial.println(); + Serial.print(F("Ping Result: Retry #:")); + Serial.print(retry); + Serial.print(F(" Ping Size (Bytes):")); + Serial.print(p_size); + Serial.print(F(" Remote Host:\"")); + Serial.print(remote_hostname); + 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.print(F("\" Time To Live (hops):")); + Serial.print(ttl); + Serial.print(F(" Round Trip (ms):")); + Serial.print(rtt); + Serial.println(); +} + +void setup() +{ + String currentOperator = ""; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Example 5 - Ping")); + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); + + // First check to see if we're connected to an operator: + if (myModule.getOperator(¤tOperator) == UBX_CELL_SUCCESS) + { + Serial.print(F("Connected to: ")); + Serial.println(currentOperator); + } + else + { + Serial.print(F("The module is not yet connected to an operator. Please use the previous examples to connect. " + "Or wait and retry. Freezing...")); + while (1) + ; // Do nothing more + } + + Serial.println(); + Serial.println(F("*** Set the Serial Monitor line ending to Newline ***")); + + Serial.println(); + Serial.println(F("Enter the name of the server you want to ping (followed by LF / Newline): ")); + Serial.println(F("Example: \"www.google.com\"")); + + // Set a callback to process the Ping result + myModule.setPingCallback(&processPingResult); +} + +void loop() +{ + if (Serial.available()) + { + char c = Serial.read(); + if (c == '\n') + { + // Newline received so let's do that ping! + Serial.println("Pinging " + pingMe + "..."); + myModule.ping(pingMe); // Use the default parameters + + // Use custom parameters + // int retries = 4; // number of retries + // int p_size = 32; // packet size (bytes) + // unsigned long timeout = 5000; // timeout (ms) + // int ttl = 32; // Time To Live + // myModule.ping(pingMe, retries, p_size, timeout, ttl); + + pingMe = ""; // Clear the server name for the next try + } + else + { + // Add serial characters to the server address + pingMe += c; + } + } + + myModule.poll(); // Keep processing data from the module so we can catch the Ping result +} \ No newline at end of file diff --git a/examples/Example6_ReceiveSMS/Example6_ReceiveSMS.ino b/examples/Example6_ReceiveSMS/Example6_ReceiveSMS.ino new file mode 100644 index 0000000..33dc6ac --- /dev/null +++ b/examples/Example6_ReceiveSMS/Example6_ReceiveSMS.ino @@ -0,0 +1,261 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular myModule; // This example works with all modules, so the base class can be used +// SparkFun_ublox_SARA_R5 myModule; // Base SARA-R5 class +// SparkFun_ublox_SARA_R500S myModule; +// SparkFun_ublox_SARA_R500S_01B myModule; +// SparkFun_ublox_SARA_R500S_61B myModule; +// SparkFun_ublox_SARA_R510M8S_61B myModule; +// SparkFun_ublox_SARA_R510S myModule; +// SparkFun_ublox_LARA_R6 myModule; // Base LARA-R6 class +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6001D myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6401D myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; +// SparkFun_ublox_LARA_R6801D myModule; + +void setup() +{ + String currentOperator = ""; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Example 6 - Receive SMS")); + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); + + // First check to see if we're connected to an operator: + if (myModule.getOperator(¤tOperator) == UBX_CELL_SUCCESS) + { + Serial.print(F("Connected to: ")); + Serial.println(currentOperator); + } + else + { + Serial.print(F("The module is not yet connected to an operator. Please use the previous examples to connect. " + "Or wait and retry. Freezing...")); + 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; + if (myModule.getPreferredMessageStorage(&used, &total) != UBX_CELL_SUCCESS) + { + Serial.println(F("An error occurred when trying to read ME memory!")); + } + else + { + if ((used != previousUsed) || printReadMessages) // Has a new message arrived? Or was the delete menu opened? + { + Serial.print(F("\r\nNumber of used memory locations: ")); + Serial.println(used); + Serial.print(F("Total number of memory locations: ")); + Serial.println(total); + Serial.println(); + + int memoryLocation = 0; + int foundMessages = 0; + // Keep reading until we find all the messages or we reach the end of the memory + while ((foundMessages < used) && (memoryLocation <= total)) + { + String unread = ""; + String from = ""; + 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 UBX_CELL_ERROR_UNEXPECTED_RESPONSE + if (myModule.readSMSmessage(memoryLocation, &unread, &from, &dateTime, &message) == UBX_CELL_SUCCESS) + { + if (printReadMessages || (unread == "REC UNREAD")) + { + Serial.print(F("Message location: ")); + Serial.println(memoryLocation); + Serial.print(F("Status: ")); + Serial.println(unread); + Serial.print(F("Originator: ")); + Serial.println(from); + Serial.print(F("Date and time: ")); + Serial.println(dateTime); + Serial.println(message); + Serial.println(); + } + foundMessages++; // We found a message + } + 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(); + } + } + + 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 (myModule.deleteSMSmessage(location) == UBX_CELL_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 (myModule.deleteReadSMSmessages() == UBX_CELL_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 (myModule.deleteReadSentSMSmessages() == UBX_CELL_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 (myModule.deleteReadSentUnsentSMSmessages() == UBX_CELL_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 (myModule.deleteAllSMSmessages() == UBX_CELL_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; + } + } +} \ No newline at end of file diff --git a/examples/Example7_SendSMS/Example7_SendSMS.ino b/examples/Example7_SendSMS/Example7_SendSMS.ino new file mode 100644 index 0000000..5e758cf --- /dev/null +++ b/examples/Example7_SendSMS/Example7_SendSMS.ino @@ -0,0 +1,131 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular myModule; // This example works with all modules, so the base class can be used +// SparkFun_ublox_SARA_R5 myModule; // Base SARA-R5 class +// SparkFun_ublox_SARA_R500S myModule; +// SparkFun_ublox_SARA_R500S_01B myModule; +// SparkFun_ublox_SARA_R500S_61B myModule; +// SparkFun_ublox_SARA_R510M8S_61B myModule; +// SparkFun_ublox_SARA_R510S myModule; +// SparkFun_ublox_LARA_R6 myModule; // Base LARA-R6 class +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6001D myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6401D myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; +// SparkFun_ublox_LARA_R6801D myModule; + +void setup() +{ + String currentOperator = ""; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Example 7 - Send SMS")); + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); + + // First check to see if we're connected to an operator: + if (myModule.getOperator(¤tOperator) == UBX_CELL_SUCCESS) + { + Serial.print(F("Connected to: ")); + Serial.println(currentOperator); + } + else + { + Serial.print(F("The module is not yet connected to an operator. Please use the previous examples to connect. " + "Or wait and retry. Freezing...")); + while (1) + ; // Do nothing more + } + + Serial.println(); + Serial.println(F("*** Set the Serial Monitor line ending to Newline ***")); +} + +void loop() +{ + String destinationNumber = ""; + String message = ""; + boolean keepGoing = true; + + Serial.println(); + Serial.println(F("Enter the destination number (followed by LF / Newline): ")); + + while (keepGoing) + { + if (Serial.available()) + { + char c = Serial.read(); + if (c == '\n') + { + keepGoing = false; // Stop if we receive a newline + } + else + { + destinationNumber += c; // Add serial characters to the destination number + } + } + } + + keepGoing = true; + Serial.println(); + Serial.println(F("Enter the message (followed by LF): ")); + + while (keepGoing) + { + if (Serial.available()) + { + char c = Serial.read(); + if (c == '\n') + { + keepGoing = false; // Stop if we receive a newline + } + else + { + message += c; // Add serial characters to the destination number + } + } + } + + // Once we receive a newline, send the text. + Serial.println("Sending: \"" + message + "\" to " + destinationNumber); + // Call myModule.sendSMS(String number, String message) to send an SMS message. + if (myModule.sendSMS(destinationNumber, message) == UBX_CELL_SUCCESS) + { + Serial.println(F("sendSMS was successful")); + } +} \ No newline at end of file diff --git a/examples/Example8_MQTT/Example8_MQTT.ino b/examples/Example8_MQTT/Example8_MQTT.ino new file mode 100644 index 0000000..1904016 --- /dev/null +++ b/examples/Example8_MQTT/Example8_MQTT.ino @@ -0,0 +1,268 @@ +#include "SparkFun_u-blox_Cellular_Arduino_Library.h" + +// Uncomment the line below that you need for Serial on your platform +#define mySerial Serial1 +// SoftwareSerial mySerial(16, 17); + +// Uncomment the module you're using. If your module is not listed below, then +// it's not supported for this example +SparkFun_ublox_Cellular myModule; // This example works with all modules, so the base class can be used +// SparkFun_ublox_SARA_R5 myModule; // Base SARA-R5 class +// SparkFun_ublox_SARA_R500S myModule; +// SparkFun_ublox_SARA_R500S_01B myModule; +// SparkFun_ublox_SARA_R500S_61B myModule; +// SparkFun_ublox_SARA_R510M8S_61B myModule; +// SparkFun_ublox_SARA_R510S myModule; +// SparkFun_ublox_LARA_R6 myModule; // Base LARA-R6 class +// SparkFun_ublox_LARA_R6001 myModule; +// SparkFun_ublox_LARA_R6001D myModule; +// SparkFun_ublox_LARA_R6401 myModule; +// SparkFun_ublox_LARA_R6401D myModule; +// SparkFun_ublox_LARA_R6801_00B myModule; +// SparkFun_ublox_LARA_R6801D myModule; + +// You can change the Quality of Service (QoS) here if you want +int qos = 0; + +// Topics that will be used for publishing and subscribing +String publishTopic; +String subscribeTopic; + +// Whether we're connected to the MQTT broker +bool mqttConnected = false; + +// Callback function for handling MQTT responses from the module +void mqttCallback(int command, int result) +{ + if (command == UBX_CELL_MQTT_COMMAND_LOGIN && result == 1) + { + // Connected to broker + mqttConnected = true; + Serial.println(F("Connected to broker!")); + } + else if (command == UBX_CELL_MQTT_COMMAND_LOGOUT && result == 1) + { + // Disconnected from broker + mqttConnected = false; + Serial.println(F("Disconnected from broker!")); + } + else if (command == UBX_CELL_MQTT_COMMAND_SUBSCRIBE && result == 1) + { + // Topic subscription successful + Serial.println(F("Subscribed to topic!")); + Serial.println(F("Enter any text to post to the topic")); + Serial.println(); + } + else if (command == UBX_CELL_MQTT_COMMAND_READ) + { + // New message available + Serial.print(F("A new message is available! Total messages to read: ")); + Serial.println(result); + Serial.println(F("Enter a blank line to read the oldest message")); + Serial.println(); + } + else + { + // Other response + Serial.print(F("Unknown MQTT reponse! Command: ")); + Serial.print(command); + Serial.print(F(" Result: ")); + Serial.println(result); + } +} + +void setup() +{ + String currentOperator = ""; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("u-blox Cellular Example 8 - MQTT")); + Serial.println(F("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(); + + Serial.println(F("Beginning...")); + + // myModule.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 + // Uncomment the next line if required + // myModule.invertPowerPin(true); + + // Initialize the module + if (myModule.begin(mySerial, UBX_CELL_DEFAULT_BAUD_RATE)) + { + Serial.println(F("Module connected!")); + } + else + { + Serial.println(F("Unable to communicate with the module.")); + Serial.println(F("Manually power-on (hold the module's On button for 3 seconds) and try again.")); + while (1) + ; // Loop forever on fail + } + Serial.println(); + + // First check to see if we're connected to an operator: + if (myModule.getOperator(¤tOperator) == UBX_CELL_SUCCESS) + { + Serial.print(F("Connected to: ")); + Serial.println(currentOperator); + } + else + { + Serial.print(F("The module is not yet connected to an operator. Please use the previous examples to connect. " + "Or wait and retry. Freezing...")); + while (1) + ; // Do nothing more + } + + Serial.println(); + + // Make sure any previous MQTT connection is closed + myModule.disconnectMQTT(); + + // Set callback for any MQTT responses from the module + myModule.setMQTTCommandCallback(mqttCallback); + + Serial.println(F("Enter the MQTT broker server name")); + + // Clear any previous input and wait for new input + while (Serial.available()) + Serial.read(); + while (!Serial.available()) + ; + + // Get the broker server name + String serverName = Serial.readStringUntil('\n'); + Serial.print(F("Server name: ")); + Serial.println(serverName); + Serial.println(); + + Serial.println(F("Enter the MQTT broker server port number")); + Serial.println(F("(or enter nothing for default port of 1883)")); + + // Clear any previous input and wait for new input + while (Serial.available()) + Serial.read(); + while (!Serial.available()) + ; + + // Get the broker server port + String serverPort = Serial.readStringUntil('\n'); + + // Attempt to parse the port number. If it fails, just set 1883 + int port = serverPort.toInt(); + if (port == 0) + port = 1883; + + Serial.print(F("Server port: ")); + Serial.println(port); + Serial.println(); + + // Now set the MQTT server + myModule.setMQTTserver(serverName, port); + + Serial.println(F("Enter the client ID")); + + // Clear any previous input and wait for new input + while (Serial.available()) + Serial.read(); + while (!Serial.available()) + ; + + // Get the client ID + String clientID = Serial.readStringUntil('\n'); + Serial.print(F("Client ID: ")); + Serial.println(clientID); + Serial.println(); + + // Set the client ID + myModule.setMQTTclientId(clientID); + + Serial.println(F("Connecting to MQTT broker...")); + myModule.connectMQTT(); + + // Wait for module to connect + while (!mqttConnected) + myModule.poll(); + + Serial.println(); + Serial.println(F("Enter a topic to publish to")); + + // Clear any previous input and wait for new input + while (Serial.available()) + Serial.read(); + while (!Serial.available()) + ; + + // Get the topic name + publishTopic = Serial.readStringUntil('\n'); + Serial.print(F("Publish topic: ")); + Serial.println(publishTopic); + Serial.println(); + + Serial.println(); + Serial.println(F("Enter a topic to subscribe to")); + + // Clear any previous input and wait for new input + while (Serial.available()) + Serial.read(); + while (!Serial.available()) + ; + + // Get the topic name + subscribeTopic = Serial.readStringUntil('\n'); + Serial.print(F("Subscribe topic: ")); + Serial.println(subscribeTopic); + Serial.println(); + + // Subscribe to the topic + myModule.subscribeMQTTtopic(qos, subscribeTopic); +} + +void loop() +{ + // Need to call poll() frequently to receive updates from the module. + myModule.poll(); + + // Check for user input + if (Serial.available()) + { + // Get user's input + String inputString = Serial.readStringUntil('\n'); + + // Clear any remaining input + while (Serial.available()) + Serial.read(); + + // Check whether the user entered anything + if (inputString.length() > 0) + { + // Publish the user's input to the topic + Serial.println(F("Publishing message:")); + Serial.println(inputString); + Serial.println(); + myModule.mqttPublishTextMsg(publishTopic, inputString.c_str()); + } + else + { + // Read next received message + uint8_t buffer[MAX_MQTT_DIRECT_MSG_LEN]; + int bytesRead = 0; + myModule.readMQTT(&qos, &subscribeTopic, buffer, MAX_MQTT_DIRECT_MSG_LEN, &bytesRead); + + // Print out message + Serial.println(F("Received message:")); + for (int i = 0; i < bytesRead; i++) + Serial.print((char)buffer[i]); + Serial.println(); + Serial.println(); + } + } +} \ No newline at end of file diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..6719043 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=SparkFun u-blox Cellular Arduino Library +version=1.0.0 +author=SparkFun Electronics +maintainer=SparkFun Electronics +sentence=Library for u-blox cellular modules +paragraph= +category=Communication +url=https://github.com/sparkfun/SparkFun_u-blox_Cellular_Arduino_Library +architectures=* diff --git a/src/SparkFun_u-blox_Cellular_Arduino_Library.h b/src/SparkFun_u-blox_Cellular_Arduino_Library.h new file mode 100644 index 0000000..b69171c --- /dev/null +++ b/src/SparkFun_u-blox_Cellular_Arduino_Library.h @@ -0,0 +1,4 @@ +#include "sfe_lara_r6.h" +#include "sfe_sara_r5.h" +#include "sfe_ublox_cellular.h" +#include "sfe_ublox_cellular_voice.h" diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h new file mode 100644 index 0000000..47af564 --- /dev/null +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -0,0 +1,385 @@ +// This file is purely for backwards compatibility with the original SARA-R5 library +// RegEx Replace: \#define\sSARA\_R5\_([A-Z0-9_]+).* \#define SARA\_R5\_$+ UBLOX\_AT\_$+ + +/* + Arduino library for the u-blox SARA-R5 LTE-M / NB-IoT modules with secure cloud, as used on the SparkFun MicroMod + Asset Tracker By: Paul Clark October 19th 2020 + + Based extensively on the: + Arduino Library for the SparkFun LTE CAT M1/NB-IoT Shield - SARA-R4 + Written by Jim Lindblom @ SparkFun Electronics, September 5, 2018 + + This Arduino library provides mechanisms to initialize and use + the SARA-R5 module over either a SoftwareSerial or hardware serial port. + + Please see LICENSE.md for the license information + +*/ + +#ifndef SPARKFUN_SARA_R5_ARDUINO_LIBRARY_H +#define SPARKFUN_SARA_R5_ARDUINO_LIBRARY_H + +#include "sfe_sara_r5.h" +#include "sfe_ublox_cellular.h" + +#define SARA_R5 SparkFun_ublox_SARA_R5 + +#define SARA_R5_POWER_PIN -1 // Default to no pin +#define SARA_R5_RESET_PIN -1 + +// 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 +#define SARA_R5_POWER_OFF_PULSE_PERIOD 3200 // Hold PWR_ON low for this long to power the module off +#define SARA_R5_POWER_ON_PULSE_PERIOD 100 // Hold PWR_ON low for this long to power the module on (SARA-R510M8S) +#define SARA_R5_RESET_PULSE_PERIOD \ + 23000 // Used to perform an abrupt emergency hardware shutdown. 23 seconds... (Yes, really!) +#define SARA_R5_POWER_OFF_TIMEOUT 40000 // Datasheet says 40 seconds... +#define SARA_R5_IP_CONNECT_TIMEOUT 130000 +#define SARA_R5_POLL_DELAY 1 +#define SARA_R5_SOCKET_WRITE_TIMEOUT 10000 + +// ## Suported AT Commands +// ### General +#define SARA_R5_COMMAND_AT UBX_CELL_COMMAND_AT +#define SARA_R5_COMMAND_ECHO UBX_CELL_COMMAND_ECHO +#define SARA_R5_COMMAND_MANU_ID UBX_CELL_COMMAND_MANU_ID +#define SARA_R5_COMMAND_MODEL_ID UBX_CELL_COMMAND_MODEL_ID +#define SARA_R5_COMMAND_FW_VER_ID UBX_CELL_COMMAND_FW_VER_ID +#define SARA_R5_COMMAND_SERIAL_NO UBX_CELL_COMMAND_SERIAL_NO +#define SARA_R5_COMMAND_IMEI UBX_CELL_COMMAND_IMEI +#define SARA_R5_COMMAND_IMSI UBX_CELL_COMMAND_IMSI +#define SARA_R5_COMMAND_CCID UBX_CELL_COMMAND_CCID +#define SARA_R5_COMMAND_REQ_CAP UBX_CELL_COMMAND_REQ_CAP +// ### Control and status +#define SARA_R5_COMMAND_POWER_OFF UBX_CELL_COMMAND_POWER_OFF +#define SARA_R5_COMMAND_FUNC UBX_CELL_COMMAND_FUNC +#define SARA_R5_COMMAND_CLOCK UBX_CELL_COMMAND_CLOCK +#define SARA_R5_COMMAND_AUTO_TZ UBX_CELL_COMMAND_AUTO_TZ +#define SARA_R5_COMMAND_TZ_REPORT UBX_CELL_COMMAND_TZ_REPORT +// ### Network service +#define SARA_R5_COMMAND_CNUM UBX_CELL_COMMAND_CNUM +#define SARA_R5_SIGNAL_QUALITY UBX_CELL_SIGNAL_QUALITY +#define SARA_R5_EXT_SIGNAL_QUALITY UBX_CELL_EXT_SIGNAL_QUALITY +#define SARA_R5_OPERATOR_SELECTION UBX_CELL_OPERATOR_SELECTION +#define SARA_R5_REGISTRATION_STATUS UBX_CELL_REGISTRATION_STATUS +#define SARA_R5_EPSREGISTRATION_STATUS UBX_CELL_EPSREGISTRATION_STATUS +#define SARA_R5_READ_OPERATOR_NAMES UBX_CELL_READ_OPERATOR_NAMES +#define SARA_R5_COMMAND_MNO UBX_CELL_COMMAND_MNO +// ### SIM +#define SARA_R5_SIM_STATE UBX_CELL_SIM_STATE +#define SARA_R5_COMMAND_SIMPIN UBX_CELL_COMMAND_SIMPIN +// ### SMS +#define SARA_R5_MESSAGE_FORMAT UBX_CELL_MESSAGE_FORMAT +#define SARA_R5_SEND_TEXT UBX_CELL_SEND_TEXT +#define SARA_R5_NEW_MESSAGE_IND UBX_CELL_NEW_MESSAGE_IND +#define SARA_R5_PREF_MESSAGE_STORE UBX_CELL_PREF_MESSAGE_STORE +#define SARA_R5_READ_TEXT_MESSAGE UBX_CELL_READ_TEXT_MESSAGE +#define SARA_R5_DELETE_MESSAGE UBX_CELL_DELETE_MESSAGE +// V24 control and V25ter (UART interface) +#define SARA_R5_FLOW_CONTROL UBX_CELL_FLOW_CONTROL +#define SARA_R5_COMMAND_BAUD UBX_CELL_COMMAND_BAUD +// ### Packet switched data services +#define SARA_R5_MESSAGE_PDP_DEF UBX_CELL_MESSAGE_PDP_DEF +#define SARA_R5_MESSAGE_PDP_CONFIG UBX_CELL_MESSAGE_PDP_CONFIG +#define SARA_R5_MESSAGE_PDP_ACTION UBX_CELL_MESSAGE_PDP_ACTION +#define SARA_R5_MESSAGE_PDP_CONTEXT_ACTIVATE UBX_CELL_MESSAGE_PDP_CONTEXT_ACTIVATE +#define SARA_R5_MESSAGE_ENTER_PPP UBX_CELL_MESSAGE_ENTER_PPP +#define SARA_R5_NETWORK_ASSIGNED_DATA UBX_CELL_NETWORK_ASSIGNED_DATA +// ### GPIO +#define SARA_R5_COMMAND_GPIO UBX_CELL_COMMAND_GPIO +// ### IP +#define SARA_R5_CREATE_SOCKET UBX_CELL_CREATE_SOCKET +#define SARA_R5_CLOSE_SOCKET UBX_CELL_CLOSE_SOCKET +#define SARA_R5_CONNECT_SOCKET UBX_CELL_CONNECT_SOCKET +#define SARA_R5_WRITE_SOCKET UBX_CELL_WRITE_SOCKET +#define SARA_R5_WRITE_UDP_SOCKET UBX_CELL_WRITE_UDP_SOCKET +#define SARA_R5_READ_SOCKET UBX_CELL_READ_SOCKET +#define SARA_R5_READ_UDP_SOCKET UBX_CELL_READ_UDP_SOCKET +#define SARA_R5_LISTEN_SOCKET UBX_CELL_LISTEN_SOCKET +#define SARA_R5_GET_ERROR UBX_CELL_GET_ERROR +#define SARA_R5_SOCKET_DIRECT_LINK UBX_CELL_SOCKET_DIRECT_LINK +#define SARA_R5_SOCKET_CONTROL UBX_CELL_SOCKET_CONTROL +#define SARA_R5_UD_CONFIGURATION UBX_CELL_UD_CONFIGURATION +// ### Ping +#define SARA_R5_PING_COMMAND UBX_CELL_PING_COMMAND +// ### HTTP +#define SARA_R5_HTTP_PROFILE UBX_CELL_HTTP_PROFILE +#define SARA_R5_HTTP_COMMAND UBX_CELL_HTTP_COMMAND +#define SARA_R5_HTTP_PROTOCOL_ERROR UBX_CELL_HTTP_PROTOCOL_ERROR + +#define SARA_R5_MQTT_NVM UBX_CELL_MQTT_NVM +#define SARA_R5_MQTT_PROFILE UBX_CELL_MQTT_PROFILE +#define SARA_R5_MQTT_COMMAND UBX_CELL_MQTT_COMMAND +#define SARA_R5_MQTT_PROTOCOL_ERROR UBX_CELL_MQTT_PROTOCOL_ERROR +// ### FTP +#define SARA_R5_FTP_PROFILE UBX_CELL_FTP_PROFILE +#define SARA_R5_FTP_COMMAND UBX_CELL_FTP_COMMAND +#define SARA_R5_FTP_PROTOCOL_ERROR UBX_CELL_FTP_PROTOCOL_ERROR +// ### GNSS +#define SARA_R5_GNSS_POWER UBX_CELL_GNSS_POWER +#define SARA_R5_GNSS_ASSISTED_IND UBX_CELL_GNSS_ASSISTED_IND +#define SARA_R5_GNSS_REQUEST_LOCATION UBX_CELL_GNSS_REQUEST_LOCATION +#define SARA_R5_GNSS_GPRMC UBX_CELL_GNSS_GPRMC +#define SARA_R5_GNSS_REQUEST_TIME UBX_CELL_GNSS_REQUEST_TIME +#define SARA_R5_GNSS_TIME_INDICATION UBX_CELL_GNSS_TIME_INDICATION +#define SARA_R5_GNSS_TIME_CONFIGURATION UBX_CELL_GNSS_TIME_CONFIGURATION +#define SARA_R5_GNSS_CONFIGURE_SENSOR UBX_CELL_GNSS_CONFIGURE_SENSOR +#define SARA_R5_GNSS_CONFIGURE_LOCATION UBX_CELL_GNSS_CONFIGURE_LOCATION +#define SARA_R5_AIDING_SERVER_CONFIGURATION UBX_CELL_AIDING_SERVER_CONFIGURATION +// ### File System +// TO DO: Add support for file tags. Default tag to USER +#define SARA_R5_FILE_SYSTEM_READ_FILE UBX_CELL_FILE_SYSTEM_READ_FILE +#define SARA_R5_FILE_SYSTEM_READ_BLOCK UBX_CELL_FILE_SYSTEM_READ_BLOCK +#define SARA_R5_FILE_SYSTEM_DOWNLOAD_FILE UBX_CELL_FILE_SYSTEM_DOWNLOAD_FILE +#define SARA_R5_FILE_SYSTEM_LIST_FILES UBX_CELL_FILE_SYSTEM_LIST_FILES +#define SARA_R5_FILE_SYSTEM_DELETE_FILE UBX_CELL_FILE_SYSTEM_DELETE_FILE +// ### File System +// TO DO: Add support for file tags. Default tag to USER +#define SARA_R5_SEC_PROFILE UBX_CELL_SEC_PROFILE +#define SARA_R5_SEC_MANAGER UBX_CELL_SEC_MANAGER + +// ### URC strings +#define SARA_R5_READ_SOCKET_URC UBX_CELL_READ_SOCKET_URC +#define SARA_R5_READ_UDP_SOCKET_URC UBX_CELL_READ_UDP_SOCKET_URC +#define SARA_R5_LISTEN_SOCKET_URC UBX_CELL_LISTEN_SOCKET_URC +#define SARA_R5_CLOSE_SOCKET_URC UBX_CELL_CLOSE_SOCKET_URC +#define SARA_R5_GNSS_REQUEST_LOCATION_URC UBX_CELL_GNSS_REQUEST_LOCATION_URC +#define SARA_R5_SIM_STATE_URC UBX_CELL_SIM_STATE_URC +#define SARA_R5_MESSAGE_PDP_ACTION_URC UBX_CELL_MESSAGE_PDP_ACTION_URC +#define SARA_R5_HTTP_COMMAND_URC UBX_CELL_HTTP_COMMAND_URC +#define SARA_R5_MQTT_COMMAND_URC UBX_CELL_MQTT_COMMAND_URC +#define SARA_R5_PING_COMMAND_URC UBX_CELL_PING_COMMAND_URC +#define SARA_R5_REGISTRATION_STATUS_URC UBX_CELL_REGISTRATION_STATUS_URC +#define SARA_R5_EPSREGISTRATION_STATUS_URC UBX_CELL_EPSREGISTRATION_STATUS_URC +#define SARA_R5_FTP_COMMAND_URC UBX_CELL_FTP_COMMAND_URC + +// ### Response +#define SARA_R5_RESPONSE_MORE UBX_CELL_RESPONSE_MORE +#define SARA_R5_RESPONSE_OK UBX_CELL_RESPONSE_OK +#define SARA_R5_RESPONSE_ERROR UBX_CELL_RESPONSE_ERROR +#define SARA_R5_RESPONSE_CONNECT UBX_CELL_RESPONSE_CONNECT +#define SARA_R5_RESPONSE_OK_OR_ERROR nullptr + +#define SARA_R5_NUM_SOCKETS 6 + +#define SARA_R5_NUM_SUPPORTED_BAUD 6 +const unsigned long SARA_R5_SUPPORTED_BAUD[SARA_R5_NUM_SUPPORTED_BAUD] = {115200, 9600, 19200, 38400, 57600, 230400}; +#define SARA_R5_DEFAULT_BAUD_RATE 115200 + +// Flow control definitions for AT&K +// Note: SW (XON/XOFF) flow control is not supported on the SARA_R5 +#define SARA_R5_DISABLE_FLOW_CONTROL UBX_CELL_DISABLE_FLOW_CONTROL +#define SARA_R5_ENABLE_FLOW_CONTROL UBX_CELL_ENABLE_FLOW_CONTROL + +#define SARA_R5_ERROR_INVALID UBX_CELL_ERROR_INVALID +#define SARA_R5_ERROR_SUCCESS UBX_CELL_ERROR_SUCCESS +#define SARA_R5_ERROR_OUT_OF_MEMORY UBX_CELL_ERROR_OUT_OF_MEMORY +#define SARA_R5_ERROR_TIMEOUT UBX_CELL_ERROR_TIMEOUT +#define SARA_R5_ERROR_UNEXPECTED_PARAM UBX_CELL_ERROR_UNEXPECTED_PARAM +#define SARA_R5_ERROR_UNEXPECTED_RESPONSE UBX_CELL_ERROR_UNEXPECTED_RESPONSE +#define SARA_R5_ERROR_NO_RESPONSE UBX_CELL_ERROR_NO_RESPONSE +#define SARA_R5_ERROR_DEREGISTERED UBX_CELL_ERROR_DEREGISTERED +#define SARA_R5_ERROR_ZERO_READ_LENGTH UBX_CELL_ERROR_ZERO_READ_LENGTH +#define SARA_R5_ERROR_ERROR UBX_CELL_ERROR_ERROR +#define SARA_R5_SUCCESS SARA_R5_ERROR_SUCCESS + +#define SARA_R5_REGISTRATION_INVALID UBX_CELL_REGISTRATION_INVALID +#define SARA_R5_REGISTRATION_NOT_REGISTERED UBX_CELL_REGISTRATION_NOT_REGISTERED +#define SARA_R5_REGISTRATION_HOME UBX_CELL_REGISTRATION_HOME +#define SARA_R5_REGISTRATION_SEARCHING UBX_CELL_REGISTRATION_SEARCHING +#define SARA_R5_REGISTRATION_DENIED UBX_CELL_REGISTRATION_DENIED +#define SARA_R5_REGISTRATION_UNKNOWN UBX_CELL_REGISTRATION_UNKNOWN +#define SARA_R5_REGISTRATION_ROAMING UBX_CELL_REGISTRATION_ROAMING +#define SARA_R5_REGISTRATION_HOME_SMS_ONLY UBX_CELL_REGISTRATION_HOME_SMS_ONLY +#define SARA_R5_REGISTRATION_ROAMING_SMS_ONLY UBX_CELL_REGISTRATION_ROAMING_SMS_ONLY +#define SARA_R5_REGISTRATION_EMERGENCY_SERV_ONLY UBX_CELL_REGISTRATION_EMERGENCY_SERV_ONLY +#define SARA_R5_REGISTRATION_HOME_CSFB_NOT_PREFERRED UBX_CELL_REGISTRATION_HOME_CSFB_NOT_PREFERRED +#define SARA_R5_REGISTRATION_ROAMING_CSFB_NOT_PREFERRED UBX_CELL_REGISTRATION_ROAMING_CSFB_NOT_PREFERRED + +#define SARA_R5_TCP UBX_CELL_TCP +#define SARA_R5_UDP UBX_CELL_UDP + +#define SARA_R5_TCP_SOCKET_STATUS_INACTIVE UBX_CELL_TCP_SOCKET_STATUS_INACTIVE +#define SARA_R5_TCP_SOCKET_STATUS_LISTEN UBX_CELL_TCP_SOCKET_STATUS_LISTEN +#define SARA_R5_TCP_SOCKET_STATUS_SYN_SENT UBX_CELL_TCP_SOCKET_STATUS_SYN_SENT +#define SARA_R5_TCP_SOCKET_STATUS_SYN_RCVD UBX_CELL_TCP_SOCKET_STATUS_SYN_RCVD +#define SARA_R5_TCP_SOCKET_STATUS_ESTABLISHED UBX_CELL_TCP_SOCKET_STATUS_ESTABLISHED +#define SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_1 UBX_CELL_TCP_SOCKET_STATUS_FIN_WAIT_1 +#define SARA_R5_TCP_SOCKET_STATUS_FIN_WAIT_2 UBX_CELL_TCP_SOCKET_STATUS_FIN_WAIT_2 +#define SARA_R5_TCP_SOCKET_STATUS_CLOSE_WAIT UBX_CELL_TCP_SOCKET_STATUS_CLOSE_WAIT +#define SARA_R5_TCP_SOCKET_STATUS_CLOSING UBX_CELL_TCP_SOCKET_STATUS_CLOSING +#define SARA_R5_TCP_SOCKET_STATUS_LAST_ACK UBX_CELL_TCP_SOCKET_STATUS_LAST_ACK +#define SARA_R5_TCP_SOCKET_STATUS_TIME_WAIT UBX_CELL_TCP_SOCKET_STATUS_TIME_WAIT + +#define SARA_R5_MESSAGE_FORMAT_PDU UBX_CELL_MESSAGE_FORMAT_PDU +#define SARA_R5_MESSAGE_FORMAT_TEXT UBX_CELL_MESSAGE_FORMAT_TEXT + +#define SARA_R5_UTIME_MODE_STOP UBX_CELL_UTIME_MODE_STOP +#define SARA_R5_UTIME_MODE_PPS UBX_CELL_UTIME_MODE_PPS +#define SARA_R5_UTIME_MODE_ONE_SHOT UBX_CELL_UTIME_MODE_ONE_SHOT +#define SARA_R5_UTIME_MODE_EXT_INT UBX_CELL_UTIME_MODE_EXT_INT + +#define SARA_R5_UTIME_SENSOR_NONE UBX_CELL_UTIME_SENSOR_NONE +#define SARA_R5_UTIME_SENSOR_GNSS_LTE UBX_CELL_UTIME_SENSOR_GNSS_LTE +#define SARA_R5_UTIME_SENSOR_LTE UBX_CELL_UTIME_SENSOR_LTE + +#define SARA_R5_UTIME_URC_CONFIGURATION_DISABLED UBX_CELL_UTIME_URC_CONFIGURATION_DISABLED +#define SARA_R5_UTIME_URC_CONFIGURATION_ENABLED UBX_CELL_UTIME_URC_CONFIGURATION_ENABLED + +#define SARA_R5_SIM_NOT_PRESENT UBX_CELL_SIM_NOT_PRESENT +#define SARA_R5_SIM_PIN_NEEDED UBX_CELL_SIM_PIN_NEEDED +#define SARA_R5_SIM_PIN_BLOCKED UBX_CELL_SIM_PIN_BLOCKED +#define SARA_R5_SIM_PUK_BLOCKED UBX_CELL_SIM_PUK_BLOCKED +#define SARA_R5_SIM_NOT_OPERATIONAL UBX_CELL_SIM_NOT_OPERATIONAL +#define SARA_R5_SIM_RESTRICTED UBX_CELL_SIM_RESTRICTED +#define SARA_R5_SIM_OPERATIONAL UBX_CELL_SIM_OPERATIONAL +// SARA_R5_SIM_PHONEBOOK_READY, // Not reported by SARA-R5 +// SARA_R5_SIM_USIM_PHONEBOOK_READY, // Not reported by SARA-R5 +// SARA_R5_SIM_TOOLKIT_REFRESH_SUCCESSFUL, // Not reported by SARA-R5 +// SARA_R5_SIM_TOOLKIT_REFRESH_UNSUCCESSFUL, // Not reported by SARA-R5 +// SARA_R5_SIM_PPP_CONNECTION_ACTIVE, // Not reported by SARA-R5 +// SARA_R5_SIM_VOICE_CALL_ACTIVE, // Not reported by SARA-R5 +// SARA_R5_SIM_CSD_CALL_ACTIVE // Not reported by SARA-R5 + +#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 + +#define SARA_R5_HTTP_OP_CODE_SERVER_IP UBX_CELL_HTTP_OP_CODE_SERVER_IP +#define SARA_R5_HTTP_OP_CODE_SERVER_NAME UBX_CELL_HTTP_OP_CODE_SERVER_NAME +#define SARA_R5_HTTP_OP_CODE_USERNAME UBX_CELL_HTTP_OP_CODE_USERNAME +#define SARA_R5_HTTP_OP_CODE_PASSWORD UBX_CELL_HTTP_OP_CODE_PASSWORD +#define SARA_R5_HTTP_OP_CODE_AUTHENTICATION UBX_CELL_HTTP_OP_CODE_AUTHENTICATION +#define SARA_R5_HTTP_OP_CODE_SERVER_PORT UBX_CELL_HTTP_OP_CODE_SERVER_PORT +#define SARA_R5_HTTP_OP_CODE_SECURE UBX_CELL_HTTP_OP_CODE_SECURE +#define SARA_R5_HTTP_OP_CODE_REQUEST_TIMEOUT UBX_CELL_HTTP_OP_CODE_REQUEST_TIMEOUT +#define SARA_R5_HTTP_OP_CODE_ADD_CUSTOM_HEADERS UBX_CELL_HTTP_OP_CODE_ADD_CUSTOM_HEADERS + +#define SARA_R5_HTTP_COMMAND_HEAD UBX_CELL_HTTP_COMMAND_HEAD +#define SARA_R5_HTTP_COMMAND_GET UBX_CELL_HTTP_COMMAND_GET +#define SARA_R5_HTTP_COMMAND_DELETE UBX_CELL_HTTP_COMMAND_DELETE +#define SARA_R5_HTTP_COMMAND_PUT UBX_CELL_HTTP_COMMAND_PUT +#define SARA_R5_HTTP_COMMAND_POST_FILE UBX_CELL_HTTP_COMMAND_POST_FILE +#define SARA_R5_HTTP_COMMAND_POST_DATA UBX_CELL_HTTP_COMMAND_POST_DATA +#define SARA_R5_HTTP_COMMAND_GET_FOTA UBX_CELL_HTTP_COMMAND_GET_FOTA + +#define SARA_R5_HTTP_CONTENT_APPLICATION_X_WWW UBX_CELL_HTTP_CONTENT_APPLICATION_X_WWW +#define SARA_R5_HTTP_CONTENT_TEXT_PLAIN UBX_CELL_HTTP_CONTENT_TEXT_PLAIN +#define SARA_R5_HTTP_CONTENT_APPLICATION_OCTET UBX_CELL_HTTP_CONTENT_APPLICATION_OCTET +#define SARA_R5_HTTP_CONTENT_MULTIPART_FORM UBX_CELL_HTTP_CONTENT_MULTIPART_FORM +#define SARA_R5_HTTP_CONTENT_APPLICATION_JSON UBX_CELL_HTTP_CONTENT_APPLICATION_JSON +#define SARA_R5_HTTP_CONTENT_APPLICATION_XML UBX_CELL_HTTP_CONTENT_APPLICATION_XML +#define SARA_R5_HTTP_CONTENT_USER_DEFINED UBX_CELL_HTTP_CONTENT_USER_DEFINED + +#define SARA_R5_MQTT_NV_RESTORE UBX_CELL_MQTT_NV_RESTORE +#define SARA_R5_MQTT_NV_SET UBX_CELL_MQTT_NV_SET +#define SARA_R5_MQTT_NV_STORE UBX_CELL_MQTT_NV_STORE + +#define SARA_R5_MQTT_PROFILE_CLIENT_ID UBX_CELL_MQTT_PROFILE_CLIENT_ID +#define SARA_R5_MQTT_PROFILE_SERVERNAME UBX_CELL_MQTT_PROFILE_SERVERNAME +#define SARA_R5_MQTT_PROFILE_IPADDRESS UBX_CELL_MQTT_PROFILE_IPADDRESS +#define SARA_R5_MQTT_PROFILE_USERNAMEPWD UBX_CELL_MQTT_PROFILE_USERNAMEPWD +#define SARA_R5_MQTT_PROFILE_QOS UBX_CELL_MQTT_PROFILE_QOS +#define SARA_R5_MQTT_PROFILE_RETAIN UBX_CELL_MQTT_PROFILE_RETAIN +#define SARA_R5_MQTT_PROFILE_TOPIC UBX_CELL_MQTT_PROFILE_TOPIC +#define SARA_R5_MQTT_PROFILE_MESSAGE UBX_CELL_MQTT_PROFILE_MESSAGE +#define SARA_R5_MQTT_PROFILE_INACTIVITYTIMEOUT UBX_CELL_MQTT_PROFILE_INACTIVITYTIMEOUT +#define SARA_R5_MQTT_PROFILE_SECURE UBX_CELL_MQTT_PROFILE_SECURE + +#define SARA_R5_MQTT_COMMAND_INVALID UBX_CELL_MQTT_COMMAND_INVALID +#define SARA_R5_MQTT_COMMAND_LOGOUT UBX_CELL_MQTT_COMMAND_LOGOUT +#define SARA_R5_MQTT_COMMAND_LOGIN UBX_CELL_MQTT_COMMAND_LOGIN +#define SARA_R5_MQTT_COMMAND_PUBLISH UBX_CELL_MQTT_COMMAND_PUBLISH +#define SARA_R5_MQTT_COMMAND_PUBLISHFILE UBX_CELL_MQTT_COMMAND_PUBLISHFILE +#define SARA_R5_MQTT_COMMAND_SUBSCRIBE UBX_CELL_MQTT_COMMAND_SUBSCRIBE +#define SARA_R5_MQTT_COMMAND_UNSUBSCRIBE UBX_CELL_MQTT_COMMAND_UNSUBSCRIBE +#define SARA_R5_MQTT_COMMAND_READ UBX_CELL_MQTT_COMMAND_READ +#define SARA_R5_MQTT_COMMAND_RCVMSGFORMAT UBX_CELL_MQTT_COMMAND_RCVMSGFORMAT +#define SARA_R5_MQTT_COMMAND_PING UBX_CELL_MQTT_COMMAND_PING +#define SARA_R5_MQTT_COMMAND_PUBLISHBINARY UBX_CELL_MQTT_COMMAND_PUBLISHBINARY + +#define SARA_R5_FTP_PROFILE_IPADDRESS UBX_CELL_FTP_PROFILE_IPADDRESS +#define SARA_R5_FTP_PROFILE_SERVERNAME UBX_CELL_FTP_PROFILE_SERVERNAME +#define SARA_R5_FTP_PROFILE_USERNAME UBX_CELL_FTP_PROFILE_USERNAME +#define SARA_R5_FTP_PROFILE_PWD UBX_CELL_FTP_PROFILE_PWD +#define SARA_R5_FTP_PROFILE_ACCOUNT UBX_CELL_FTP_PROFILE_ACCOUNT +#define SARA_R5_FTP_PROFILE_TIMEOUT UBX_CELL_FTP_PROFILE_TIMEOUT +#define SARA_R5_FTP_PROFILE_MODE UBX_CELL_FTP_PROFILE_MODE + +#define SARA_R5_FTP_COMMAND_INVALID UBX_CELL_FTP_COMMAND_INVALID +#define SARA_R5_FTP_COMMAND_LOGOUT UBX_CELL_FTP_COMMAND_LOGOUT +#define SARA_R5_FTP_COMMAND_LOGIN UBX_CELL_FTP_COMMAND_LOGIN +#define SARA_R5_FTP_COMMAND_DELETE_FILE UBX_CELL_FTP_COMMAND_DELETE_FILE +#define SARA_R5_FTP_COMMAND_RENAME_FILE UBX_CELL_FTP_COMMAND_RENAME_FILE +#define SARA_R5_FTP_COMMAND_GET_FILE UBX_CELL_FTP_COMMAND_GET_FILE +#define SARA_R5_FTP_COMMAND_PUT_FILE UBX_CELL_FTP_COMMAND_PUT_FILE +#define SARA_R5_FTP_COMMAND_GET_FILE_DIRECT UBX_CELL_FTP_COMMAND_GET_FILE_DIRECT +#define SARA_R5_FTP_COMMAND_PUT_FILE_DIRECT UBX_CELL_FTP_COMMAND_PUT_FILE_DIRECT +#define SARA_R5_FTP_COMMAND_CHANGE_DIR UBX_CELL_FTP_COMMAND_CHANGE_DIR +#define SARA_R5_FTP_COMMAND_MKDIR UBX_CELL_FTP_COMMAND_MKDIR +#define SARA_R5_FTP_COMMAND_RMDIR UBX_CELL_FTP_COMMAND_RMDIR +#define SARA_R5_FTP_COMMAND_DIR_INFO UBX_CELL_FTP_COMMAND_DIR_INFO +#define SARA_R5_FTP_COMMAND_LS UBX_CELL_FTP_COMMAND_LS +#define SARA_R5_FTP_COMMAND_GET_FOTA_FILE UBX_CELL_FTP_COMMAND_GET_FOTA_FILE + +#define SARA_R5_PSD_CONFIG_PARAM_PROTOCOL UBX_CELL_PSD_CONFIG_PARAM_PROTOCOL +#define SARA_R5_PSD_CONFIG_PARAM_APN UBX_CELL_PSD_CONFIG_PARAM_APN +// SARA_R5_PSD_CONFIG_PARAM_USERNAME, // Not allowed on SARA-R5 +// SARA_R5_PSD_CONFIG_PARAM_PASSWORD, // Not allowed on SARA-R5 +#define SARA_R5_PSD_CONFIG_PARAM_DNS1 UBX_CELL_PSD_CONFIG_PARAM_DNS1 +#define SARA_R5_PSD_CONFIG_PARAM_DNS2 UBX_CELL_PSD_CONFIG_PARAM_DNS2 +// SARA_R5_PSD_CONFIG_PARAM_AUTHENTICATION, // Not allowed on SARA-R5 +// SARA_R5_PSD_CONFIG_PARAM_IP_ADDRESS, // Not allowed on SARA-R5 +// SARA_R5_PSD_CONFIG_PARAM_DATA_COMPRESSION, // Not allowed on SARA-R5 +// SARA_R5_PSD_CONFIG_PARAM_HEADER_COMPRESSION, // Not allowed on SARA-R5 +#define SARA_R5_PSD_CONFIG_PARAM_MAP_TO_CID UBX_CELL_PSD_CONFIG_PARAM_MAP_TO_CID + +#define SARA_R5_PSD_PROTOCOL_IPV4 UBX_CELL_PSD_PROTOCOL_IPV4 +#define SARA_R5_PSD_PROTOCOL_IPV6 UBX_CELL_PSD_PROTOCOL_IPV6 +#define SARA_R5_PSD_PROTOCOL_IPV4V6_V4_PREF UBX_CELL_PSD_PROTOCOL_IPV4V6_V4_PREF +#define SARA_R5_PSD_PROTOCOL_IPV4V6_V6_PREF UBX_CELL_PSD_PROTOCOL_IPV4V6_V6_PREF + +#define SARA_R5_PSD_ACTION_RESET UBX_CELL_PSD_ACTION_RESET +#define SARA_R5_PSD_ACTION_STORE UBX_CELL_PSD_ACTION_STORE +#define SARA_R5_PSD_ACTION_LOAD UBX_CELL_PSD_ACTION_LOAD +#define SARA_R5_PSD_ACTION_ACTIVATE UBX_CELL_PSD_ACTION_ACTIVATE +#define SARA_R5_PSD_ACTION_DEACTIVATE UBX_CELL_PSD_ACTION_DEACTIVATE + +#define SARA_R5_SEC_PROFILE_PARAM_CERT_VAL_LEVEL UBX_CELL_SEC_PROFILE_PARAM_CERT_VAL_LEVEL +#define SARA_R5_SEC_PROFILE_PARAM_TLS_VER UBX_CELL_SEC_PROFILE_PARAM_TLS_VER +#define SARA_R5_SEC_PROFILE_PARAM_CYPHER_SUITE UBX_CELL_SEC_PROFILE_PARAM_CYPHER_SUITE +#define SARA_R5_SEC_PROFILE_PARAM_ROOT_CA UBX_CELL_SEC_PROFILE_PARAM_ROOT_CA +#define SARA_R5_SEC_PROFILE_PARAM_HOSTNAME UBX_CELL_SEC_PROFILE_PARAM_HOSTNAME +#define SARA_R5_SEC_PROFILE_PARAM_CLIENT_CERT UBX_CELL_SEC_PROFILE_PARAM_CLIENT_CERT +#define SARA_R5_SEC_PROFILE_PARAM_CLIENT_KEY UBX_CELL_SEC_PROFILE_PARAM_CLIENT_KEY +#define SARA_R5_SEC_PROFILE_PARAM_CLIENT_KEY_PWD UBX_CELL_SEC_PROFILE_PARAM_CLIENT_KEY_PWD +#define SARA_R5_SEC_PROFILE_PARAM_PSK UBX_CELL_SEC_PROFILE_PARAM_PSK +#define SARA_R5_SEC_PROFILE_PARAM_PSK_IDENT UBX_CELL_SEC_PROFILE_PARAM_PSK_IDENT +#define SARA_R5_SEC_PROFILE_PARAM_SNI UBX_CELL_SEC_PROFILE_PARAM_SNI + +#define SARA_R5_SEC_PROFILE_CERTVAL_OPCODE_NO UBX_CELL_SEC_PROFILE_CERTVAL_OPCODE_NO +#define SARA_R5_SEC_PROFILE_CERTVAL_OPCODE_YESNOURL UBX_CELL_SEC_PROFILE_CERTVAL_OPCODE_YESNOURL +#define SARA_R5_SEC_PROFILE_CERVTAL_OPCODE_YESURL UBX_CELL_SEC_PROFILE_CERVTAL_OPCODE_YESURL +#define SARA_R5_SEC_PROFILE_CERTVAL_OPCODE_YESURLDATE UBX_CELL_SEC_PROFILE_CERTVAL_OPCODE_YESURLDATE + +#define SARA_R5_SEC_PROFILE_TLS_OPCODE_ANYVER UBX_CELL_SEC_PROFILE_TLS_OPCODE_ANYVER +#define SARA_R5_SEC_PROFILE_TLS_OPCODE_VER1_0 UBX_CELL_SEC_PROFILE_TLS_OPCODE_VER1_0 +#define SARA_R5_SEC_PROFILE_TLS_OPCODE_VER1_1 UBX_CELL_SEC_PROFILE_TLS_OPCODE_VER1_1 +#define SARA_R5_SEC_PROFILE_TLS_OPCODE_VER1_2 UBX_CELL_SEC_PROFILE_TLS_OPCODE_VER1_2 +#define SARA_R5_SEC_PROFILE_TLS_OPCODE_VER1_3 UBX_CELL_SEC_PROFILE_TLS_OPCODE_VER1_3 + +#define SARA_R5_SEC_PROFILE_SUITE_OPCODE_PROPOSEDDEFAULT UBX_CELL_SEC_PROFILE_SUITE_OPCODE_PROPOSEDDEFAULT + +#define SARA_R5_SEC_MANAGER_OPCODE_IMPORT UBX_CELL_SEC_MANAGER_OPCODE_IMPORT + +#define SARA_R5_SEC_MANAGER_ROOTCA UBX_CELL_SEC_MANAGER_ROOTCA +#define SARA_R5_SEC_MANAGER_CLIENT_CERT UBX_CELL_SEC_MANAGER_CLIENT_CERT +#define SARA_R5_SEC_MANAGER_CLIENT_KEY UBX_CELL_SEC_MANAGER_CLIENT_KEY +#define SARA_R5_SEC_MANAGER_SERVER_CERT UBX_CELL_SEC_MANAGER_SERVER_CERT + +#endif // SPARKFUN_SARA_R5_ARDUINO_LIBRARY_H diff --git a/src/sfe_lara_r6.h b/src/sfe_lara_r6.h new file mode 100644 index 0000000..e421ae3 --- /dev/null +++ b/src/sfe_lara_r6.h @@ -0,0 +1,36 @@ +#ifndef SFE_LARA_R6_LIBRARY_H +#define SFE_LARA_R6_LIBRARY_H + +#include "sfe_ublox_cellular.h" +#include "sfe_ublox_cellular_voice.h" + +// Base LARA-R6 class +class SparkFun_ublox_LARA_R6 : public SparkFun_ublox_Cellular +{ +}; + +class SparkFun_ublox_LARA_R6001 : public SparkFun_ublox_LARA_R6, public SparkFun_ublox_Cellular_Voice_Base +{ +}; + +class SparkFun_ublox_LARA_R6001D : public SparkFun_ublox_LARA_R6 +{ +}; + +class SparkFun_ublox_LARA_R6401 : public SparkFun_ublox_LARA_R6, public SparkFun_ublox_Cellular_Voice_Base +{ +}; + +class SparkFun_ublox_LARA_R6401D : public SparkFun_ublox_LARA_R6 +{ +}; + +class SparkFun_ublox_LARA_R6801_00B : public SparkFun_ublox_LARA_R6, public SparkFun_ublox_Cellular_Voice_Base +{ +}; + +class SparkFun_ublox_LARA_R6801D : public SparkFun_ublox_LARA_R6 +{ +}; + +#endif \ No newline at end of file diff --git a/src/sfe_sara_r5.cpp b/src/sfe_sara_r5.cpp new file mode 100644 index 0000000..850b38f --- /dev/null +++ b/src/sfe_sara_r5.cpp @@ -0,0 +1,330 @@ +#include "sfe_sara_r5.h" + +SparkFun_ublox_SARA_R5::SparkFun_ublox_SARA_R5() +{ + addURCHandler(UBX_CELL_MESSAGE_PDP_ACTION_URC, + [this](const char *event) { return this->urcHandlerPDPAction(event); }); +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::setUtimeMode(UBX_CELL_utime_mode_t mode, UBX_CELL_utime_sensor_t sensor) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_REQUEST_TIME) + 16; + char command[cmdLen]; + + if (mode == UBX_CELL_UTIME_MODE_STOP) // stop UTIME does not require a sensor + snprintf(command, cmdLen, "%s=%d", UBX_CELL_GNSS_REQUEST_TIME, mode); + else + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_GNSS_REQUEST_TIME, mode, sensor); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_10_SEC_TIMEOUT); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::getUtimeMode(UBX_CELL_utime_mode_t *mode, UBX_CELL_utime_sensor_t *sensor) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_REQUEST_TIME) + 2; + char command[cmdLen]; + char response[minimumResponseAllocation]; + + UBX_CELL_utime_mode_t m; + UBX_CELL_utime_sensor_t s; + + snprintf(command, cmdLen, "%s?", UBX_CELL_GNSS_REQUEST_TIME); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_10_SEC_TIMEOUT); + + // Response format: \r\n+UTIME: [,]\r\n\r\nOK\r\n + if (err == UBX_CELL_ERROR_SUCCESS) + { + int mStore, sStore, scanned = 0; + char *searchPtr = strnstr(response, "+UTIME:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+UTIME:"); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%d,%d\r\n", &mStore, &sStore); + } + m = (UBX_CELL_utime_mode_t)mStore; + s = (UBX_CELL_utime_sensor_t)sStore; + if (scanned == 2) + { + *mode = m; + *sensor = s; + } + else if (scanned == 1) + { + *mode = m; + *sensor = UBX_CELL_UTIME_SENSOR_NONE; + } + else + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::setUtimeIndication(UBX_CELL_utime_urc_configuration_t config) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_TIME_INDICATION) + 16; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_GNSS_TIME_INDICATION, config); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::getUtimeIndication(UBX_CELL_utime_urc_configuration_t *config) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_TIME_INDICATION) + 2; + char command[cmdLen]; + char response[minimumResponseAllocation]; + + UBX_CELL_utime_urc_configuration_t c; + + snprintf(command, cmdLen, "%s?", UBX_CELL_GNSS_TIME_INDICATION); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + // Response format: \r\n+UTIMEIND: \r\n\r\nOK\r\n + if (err == UBX_CELL_ERROR_SUCCESS) + { + int cStore, scanned = 0; + char *searchPtr = strnstr(response, "+UTIMEIND:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+UTIMEIND:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%d\r\n", &cStore); + } + c = (UBX_CELL_utime_urc_configuration_t)cStore; + if (scanned == 1) + { + *config = c; + } + else + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::setUtimeConfiguration(int32_t offsetNanoseconds, int32_t offsetSeconds) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_TIME_CONFIGURATION) + 48; + char command[cmdLen]; + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_GNSS_TIME_CONFIGURATION, offsetNanoseconds, offsetSeconds); +#else + snprintf(command, cmdLen, "%s=%ld,%ld", UBX_CELL_GNSS_TIME_CONFIGURATION, offsetNanoseconds, offsetSeconds); +#endif + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::getUtimeConfiguration(int32_t *offsetNanoseconds, int32_t *offsetSeconds) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_TIME_CONFIGURATION) + 2; + char command[cmdLen]; + char response[minimumResponseAllocation]; + + int32_t ons; + int32_t os; + + snprintf(command, cmdLen, "%s?", UBX_CELL_GNSS_TIME_CONFIGURATION); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + // Response format: \r\n+UTIMECFG: ,\r\n\r\nOK\r\n + if (err == UBX_CELL_ERROR_SUCCESS) + { + int scanned = 0; + char *searchPtr = strnstr(response, "+UTIMECFG:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+UTIMECFG:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + scanned = sscanf(searchPtr, "%d,%d\r\n", &ons, &os); +#else + scanned = sscanf(searchPtr, "%ld,%ld\r\n", &ons, &os); +#endif + } + if (scanned == 2) + { + *offsetNanoseconds = ons; + *offsetSeconds = os; + } + else + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::setPDPconfiguration(int profile, UBX_CELL_pdp_configuration_parameter_t parameter, int value) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MESSAGE_PDP_CONFIG) + 24; + char command[cmdLen]; + + if (profile >= UBX_CELL_NUM_PSD_PROFILES) + return UBX_CELL_ERROR_ERROR; + + snprintf(command, cmdLen, "%s=%d,%d,%d", UBX_CELL_MESSAGE_PDP_CONFIG, profile, parameter, value); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::setPDPconfiguration(int profile, UBX_CELL_pdp_configuration_parameter_t parameter, + UBX_CELL_pdp_protocol_type_t value) +{ + return (setPDPconfiguration(profile, parameter, (int)value)); +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::setPDPconfiguration(int profile, UBX_CELL_pdp_configuration_parameter_t parameter, + String value) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MESSAGE_PDP_CONFIG) + 64; + char command[cmdLen]; + + if (profile >= UBX_CELL_NUM_PSD_PROFILES) + return UBX_CELL_ERROR_ERROR; + + snprintf(command, cmdLen, "%s=%d,%d,\"%s\"", UBX_CELL_MESSAGE_PDP_CONFIG, profile, parameter, value.c_str()); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::setPDPconfiguration(int profile, UBX_CELL_pdp_configuration_parameter_t parameter, + IPAddress value) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MESSAGE_PDP_CONFIG) + 64; + char command[cmdLen]; + + if (profile >= UBX_CELL_NUM_PSD_PROFILES) + return UBX_CELL_ERROR_ERROR; + + snprintf(command, cmdLen, "%s=%d,%d,\"%d.%d.%d.%d\"", UBX_CELL_MESSAGE_PDP_CONFIG, profile, parameter, value[0], + value[1], value[2], value[3]); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::performPDPaction(int profile, UBX_CELL_pdp_actions_t action) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MESSAGE_PDP_ACTION) + 32; + char command[cmdLen]; + + if (profile >= UBX_CELL_NUM_PSD_PROFILES) + return UBX_CELL_ERROR_ERROR; + + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_MESSAGE_PDP_ACTION, profile, action); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_SARA_R5::getNetworkAssignedIPAddress(int profile, IPAddress *address) +{ + size_t cmdLen = strlen(UBX_CELL_NETWORK_ASSIGNED_DATA) + 16; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int scanNum = 0; + int profileStore = 0; + int paramTag = 0; // 0: IP address: dynamic IP address assigned during PDP context activation + int paramVals[4]; + + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_NETWORK_ASSIGNED_DATA, profile, paramTag); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+UPSND:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+UPSND:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,%d,\"%d.%d.%d.%d\"", &profileStore, ¶mTag, ¶mVals[0], ¶mVals[1], + ¶mVals[2], ¶mVals[3]); + } + if (scanNum != 6) + { + if (_printDebug == true) + { + _debugPort->print(F("getNetworkAssignedIPAddress: error: scanNum is ")); + _debugPort->println(scanNum); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + IPAddress tempAddress = {(uint8_t)paramVals[0], (uint8_t)paramVals[1], (uint8_t)paramVals[2], + (uint8_t)paramVals[3]}; + *address = tempAddress; + } + + return err; +} + +bool SparkFun_ublox_SARA_R5::urcHandlerPDPAction(const char *event) +{ + // URC: +UUPSDA (Packet Switched Data Action) + int result; + IPAddress remoteIP = {0, 0, 0, 0}; + int scanNum; + int remoteIPstore[4]; + + char *searchPtr = strnstr(event, UBX_CELL_MESSAGE_PDP_ACTION_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_MESSAGE_PDP_ACTION_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,\"%d.%d.%d.%d\"", &result, &remoteIPstore[0], &remoteIPstore[1], + &remoteIPstore[2], &remoteIPstore[3]); + + if (scanNum == 5) + { + if (_printDebug == true) + _debugPort->println(F("processReadEvent: packet switched data action")); + + for (int i = 0; i <= 3; i++) + { + remoteIP[i] = (uint8_t)remoteIPstore[i]; + } + + if (_psdActionRequestCallback != nullptr) + { + _psdActionRequestCallback(result, remoteIP); + } + + return true; + } + } + + return false; +} diff --git a/src/sfe_sara_r5.h b/src/sfe_sara_r5.h new file mode 100644 index 0000000..f61a67f --- /dev/null +++ b/src/sfe_sara_r5.h @@ -0,0 +1,73 @@ +#ifndef SFE_SARA_R5_LIBRARY_H +#define SFE_SARA_R5_LIBRARY_H + +#include "sfe_ublox_cellular.h" + +const char *const UBX_CELL_MESSAGE_PDP_CONFIG = "+UPSD"; // Packet switched Data Profile configuration +const char *const UBX_CELL_MESSAGE_PDP_ACTION = "+UPSDA"; // Perform the action for the specified PSD profile +const char *const UBX_CELL_NETWORK_ASSIGNED_DATA = "+UPSND"; // Packet switched network-assigned data +const char *const UBX_CELL_GNSS_REQUEST_TIME = "+UTIME"; // Ask for time information from cellular modem (CellTime) +const char *const UBX_CELL_GNSS_TIME_INDICATION = "+UTIMEIND"; // Time information request status unsolicited indication +const char *const UBX_CELL_GNSS_TIME_CONFIGURATION = "+UTIMECFG"; // Sets time configuration + +const char *const UBX_CELL_MESSAGE_PDP_ACTION_URC = "+UUPSDA:"; + +// Base SARA-R5 class +class SparkFun_ublox_SARA_R5 : public SparkFun_ublox_Cellular +{ + public: + SparkFun_ublox_SARA_R5(); + + UBX_CELL_error_t setUtimeMode( + UBX_CELL_utime_mode_t mode = UBX_CELL_UTIME_MODE_PPS, + UBX_CELL_utime_sensor_t sensor = UBX_CELL_UTIME_SENSOR_GNSS_LTE); // Time mode, source etc. (+UTIME) + UBX_CELL_error_t getUtimeMode(UBX_CELL_utime_mode_t *mode, UBX_CELL_utime_sensor_t *sensor); + UBX_CELL_error_t setUtimeIndication( + UBX_CELL_utime_urc_configuration_t config = UBX_CELL_UTIME_URC_CONFIGURATION_ENABLED); // +UTIMEIND + UBX_CELL_error_t getUtimeIndication(UBX_CELL_utime_urc_configuration_t *config); + UBX_CELL_error_t setUtimeConfiguration(int32_t offsetNanoseconds = 0, int32_t offsetSeconds = 0); // +UTIMECFG + UBX_CELL_error_t getUtimeConfiguration(int32_t *offsetNanoseconds, int32_t *offsetSeconds); + + // Packet Switched Data + // Configure the PDP using +UPSD. See UBX_CELL_pdp_configuration_parameter_t for the list of parameters: protocol, + // APN, username, DNS, etc. + UBX_CELL_error_t setPDPconfiguration(int profile, UBX_CELL_pdp_configuration_parameter_t parameter, + int value); // Set parameters in the chosen PSD profile + UBX_CELL_error_t setPDPconfiguration( + int profile, UBX_CELL_pdp_configuration_parameter_t parameter, + UBX_CELL_pdp_protocol_type_t value); // Set parameters in the chosen PSD profile + UBX_CELL_error_t setPDPconfiguration(int profile, UBX_CELL_pdp_configuration_parameter_t parameter, + String value); // Set parameters in the chosen PSD profile + UBX_CELL_error_t setPDPconfiguration(int profile, UBX_CELL_pdp_configuration_parameter_t parameter, + IPAddress value); // Set parameters in the chosen PSD profile + UBX_CELL_error_t performPDPaction( + int profile, UBX_CELL_pdp_actions_t action); // Performs the requested action for the specified PSD profile: + // reset, store, load, activate, deactivate + UBX_CELL_error_t getNetworkAssignedIPAddress( + int profile, IPAddress *address); // Get the dynamic IP address assigned during PDP context activation + + protected: + bool urcHandlerPDPAction(const char *event); +}; + +class SparkFun_ublox_SARA_R500S : public SparkFun_ublox_SARA_R5 +{ +}; + +class SparkFun_ublox_SARA_R500S_01B : public SparkFun_ublox_SARA_R5 +{ +}; + +class SparkFun_ublox_SARA_R500S_61B : public SparkFun_ublox_SARA_R5 +{ +}; + +class SparkFun_ublox_SARA_R510M8S_61B : public SparkFun_ublox_SARA_R5 +{ +}; + +class SparkFun_ublox_SARA_R510S : public SparkFun_ublox_SARA_R5 +{ +}; + +#endif \ No newline at end of file diff --git a/src/sfe_ublox_cellular.cpp b/src/sfe_ublox_cellular.cpp new file mode 100644 index 0000000..1e7e2db --- /dev/null +++ b/src/sfe_ublox_cellular.cpp @@ -0,0 +1,6011 @@ +/* + Arduino library for the u-blox SARA-R5 LTE-M / NB-IoT modules with secure cloud, as used on the SparkFun MicroMod + Asset Tracker By: Paul Clark October 19th 2020 + + Based extensively on the: + Arduino Library for the SparkFun LTE CAT M1/NB-IoT Shield - SARA-R4 + Written by Jim Lindblom @ SparkFun Electronics, September 5, 2018 + + This Arduino library provides mechanisms to initialize and use + the SARA-R5 module over either a SoftwareSerial or hardware serial port. + + Please see LICENSE.md for the license information + +*/ + +#include "sfe_ublox_cellular.h" + +SparkFun_ublox_Cellular::SparkFun_ublox_Cellular(int powerPin, int resetPin, uint8_t maxInitTries) +{ +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + _softSerial = nullptr; +#endif + _hardSerial = nullptr; + _baud = 0; + _resetPin = resetPin; + _powerPin = powerPin; + _invertPowerPin = false; + _maxInitTries = maxInitTries; + _socketListenCallback = nullptr; + _socketReadCallback = nullptr; + _socketReadCallbackPlus = nullptr; + _socketCloseCallback = nullptr; + _gpsRequestCallback = nullptr; + _simStateReportCallback = nullptr; + _psdActionRequestCallback = nullptr; + _pingRequestCallback = nullptr; + _httpCommandRequestCallback = nullptr; + _mqttCommandRequestCallback = nullptr; + _registrationCallback = nullptr; + _epsRegistrationCallback = nullptr; + _debugAtPort = nullptr; + _debugPort = nullptr; + _printDebug = false; + _lastRemoteIP = {0, 0, 0, 0}; + _lastLocalIP = {0, 0, 0, 0}; + for (int i = 0; i < UBX_CELL_NUM_SOCKETS; i++) + _lastSocketProtocol[i] = 0; // Set to zero initially. Will be set to TCP/UDP by socketOpen etc. + _autoTimeZoneForBegin = true; + _bufferedPollReentrant = false; + _pollReentrant = false; + _saraResponseBacklogLength = 0; + _saraRXBuffer = nullptr; + _pruneBuffer = nullptr; + _saraResponseBacklog = nullptr; + + // Add base URC handlers + addURCHandler(UBX_CELL_READ_SOCKET_URC, [this](const char *event) { return this->urcHandlerReadSocket(event); }); + addURCHandler(UBX_CELL_READ_UDP_SOCKET_URC, + [this](const char *event) { return this->urcHandlerReadUDPSocket(event); }); + addURCHandler(UBX_CELL_LISTEN_SOCKET_URC, + [this](const char *event) { return this->urcHandlerListeningSocket(event); }); + addURCHandler(UBX_CELL_CLOSE_SOCKET_URC, [this](const char *event) { return this->urcHandlerCloseSocket(event); }); + addURCHandler(UBX_CELL_GNSS_REQUEST_LOCATION_URC, + [this](const char *event) { return this->urcHandlerGNSSRequestLocation(event); }); + addURCHandler(UBX_CELL_SIM_STATE_URC, [this](const char *event) { return this->urcHandlerSIMState(event); }); + addURCHandler(UBX_CELL_HTTP_COMMAND_URC, [this](const char *event) { return this->urcHandlerHTTPCommand(event); }); + addURCHandler(UBX_CELL_MQTT_COMMAND_URC, [this](const char *event) { return this->urcHandlerMQTTCommand(event); }); + addURCHandler(UBX_CELL_PING_COMMAND_URC, [this](const char *event) { return this->urcHandlerPingCommand(event); }); + addURCHandler(UBX_CELL_FTP_COMMAND_URC, [this](const char *event) { return this->urcHandlerFTPCommand(event); }); + addURCHandler(UBX_CELL_REGISTRATION_STATUS_URC, + [this](const char *event) { return this->urcHandlerRegistrationStatus(event); }); + addURCHandler(UBX_CELL_EPSREGISTRATION_STATUS_URC, + [this](const char *event) { return this->urcHandlerEPSRegistrationStatus(event); }); +} + +SparkFun_ublox_Cellular::~SparkFun_ublox_Cellular(void) +{ + if (nullptr != _saraRXBuffer) + { + delete[] _saraRXBuffer; + _saraRXBuffer = nullptr; + } + if (nullptr != _pruneBuffer) + { + delete[] _pruneBuffer; + _pruneBuffer = nullptr; + } + if (nullptr != _saraResponseBacklog) + { + delete[] _saraResponseBacklog; + _saraResponseBacklog = nullptr; + } +} + +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED +bool SparkFun_ublox_Cellular::begin(SoftwareSerial &softSerial, unsigned long baud) +{ + if (nullptr == _saraRXBuffer) + { + _saraRXBuffer = new char[_RXBuffSize]; + if (nullptr == _saraRXBuffer) + { + if (_printDebug == true) + _debugPort->println(F("begin: not enough memory for _saraRXBuffer!")); + return false; + } + } + memset(_saraRXBuffer, 0, _RXBuffSize); + + if (nullptr == _pruneBuffer) + { + _pruneBuffer = new char[_RXBuffSize]; + if (nullptr == _pruneBuffer) + { + if (_printDebug == true) + _debugPort->println(F("begin: not enough memory for _pruneBuffer!")); + return false; + } + } + memset(_pruneBuffer, 0, _RXBuffSize); + + if (nullptr == _saraResponseBacklog) + { + _saraResponseBacklog = new char[_RXBuffSize]; + if (nullptr == _saraResponseBacklog) + { + if (_printDebug == true) + _debugPort->println(F("begin: not enough memory for _saraResponseBacklog!")); + return false; + } + } + memset(_saraResponseBacklog, 0, _RXBuffSize); + + UBX_CELL_error_t err; + + _softSerial = &softSerial; + + err = init(baud); + if (err == UBX_CELL_ERROR_SUCCESS) + { + return true; + } + return false; +} +#endif + +bool SparkFun_ublox_Cellular::begin(HardwareSerial &hardSerial, unsigned long baud) +{ + if (nullptr == _saraRXBuffer) + { + _saraRXBuffer = new char[_RXBuffSize]; + if (nullptr == _saraRXBuffer) + { + if (_printDebug == true) + _debugPort->println(F("begin: not enough memory for _saraRXBuffer!")); + return false; + } + } + memset(_saraRXBuffer, 0, _RXBuffSize); + + if (nullptr == _pruneBuffer) + { + _pruneBuffer = new char[_RXBuffSize]; + if (nullptr == _pruneBuffer) + { + if (_printDebug == true) + _debugPort->println(F("begin: not enough memory for _pruneBuffer!")); + return false; + } + } + memset(_pruneBuffer, 0, _RXBuffSize); + + if (nullptr == _saraResponseBacklog) + { + _saraResponseBacklog = new char[_RXBuffSize]; + if (nullptr == _saraResponseBacklog) + { + if (_printDebug == true) + _debugPort->println(F("begin: not enough memory for _saraResponseBacklog!")); + return false; + } + } + memset(_saraResponseBacklog, 0, _RXBuffSize); + + UBX_CELL_error_t err; + + _hardSerial = &hardSerial; + + err = init(baud); + if (err == UBX_CELL_ERROR_SUCCESS) + { + return true; + } + return false; +} + +// Calling this function with nothing sets the debug port to Serial +// You can also call it with other streams like Serial1, SerialUSB, etc. +void SparkFun_ublox_Cellular::enableDebugging(Print &debugPort) +{ + _debugPort = &debugPort; + _printDebug = true; +} + +// Calling this function with nothing sets the debug port to Serial +// You can also call it with other streams like Serial1, SerialUSB, etc. +void SparkFun_ublox_Cellular::enableAtDebugging(Print &debugPort) +{ + _debugAtPort = &debugPort; + _printAtDebug = true; +} + +// 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 +bool SparkFun_ublox_Cellular::bufferedPoll(void) +{ + if (_bufferedPollReentrant == true) // Check for reentry (i.e. bufferedPoll has been called from inside a callback) + return false; + + _bufferedPollReentrant = true; + + int avail = 0; + char c = 0; + bool handled = false; + unsigned long timeIn = millis(); + char *event; + int backlogLen = _saraResponseBacklogLength; + + 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 (_saraResponseBacklogLength > 0) + { + // The backlog also logs reads from other tasks like transmitting. + if (_printDebug == true) + { + _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. + { + // Check for incoming serial data. Copy it into the backlog + + // Important note: + // On ESP32, Serial.available only provides an update every ~120 bytes during the reception of long messages: + // https://gitter.im/espressif/arduino-esp32?at=5e25d6370a1cf54144909c85 + // Be aware that if a long message is being received, the code below will timeout after _rxWindowMillis = 2 + // millis. At 115200 baud, hwAvailable takes ~120 * 10 / 115200 = 10.4 millis before it indicates that data is + // being received. + + while (((millis() - timeIn) < _rxWindowMillis) && (avail < _RXBuffSize)) + { + if (hwAvailable() > 0) // hwAvailable can return -1 if the serial port is NULL + { + c = readChar(); + // 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(); + } + else + { + yield(); + } + } + + // _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 + + char *preservedEvent; + event = strtok_r(_saraRXBuffer, "\r\n", + &preservedEvent); // Look for an 'event' (_saraRXBuffer contains something ending in \r\n) + + if (event != nullptr) + if (_printDebug == true) + _debugPort->println(F("bufferedPoll: event(s) found! ===>")); + + while (event != nullptr) // Keep going until all events have been processed + { + if (_printDebug == true) + { + _debugPort->print(F("bufferedPoll: start of event: ")); + _debugPort->println(event); + } + + // Process the event + bool latestHandled = processURCEvent((const char *)event); + if (latestHandled) + { + if ((true == _printAtDebug) && (nullptr != event)) + { + _debugAtPort->print(event); + } + handled = true; // handled will be true if latestHandled has ever been true + } + 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, _saraResponseBacklogLength); + avail += _saraResponseBacklogLength; + memset(_saraResponseBacklog, 0, _RXBuffSize); // Clear out the backlog buffer again. + _saraResponseBacklogLength = 0; + } + + // Walk through any remaining events + event = strtok_r(nullptr, "\r\n", &preservedEvent); + + if (_printDebug == true) + _debugPort->println(F("bufferedPoll: end of event")); // Just to denote end of processing event. + + if (event == nullptr) + if (_printDebug == true) + _debugPort->println(F("bufferedPoll: <=== end of event(s)!")); + } + } + + _bufferedPollReentrant = false; + + return handled; +} // /bufferedPoll + +bool SparkFun_ublox_Cellular::urcHandlerReadSocket(const char *event) +{ + // URC: +UUSORD (Read Socket Data) + int socket, length; + char *searchPtr = strnstr(event, UBX_CELL_READ_SOCKET_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_READ_SOCKET_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // Skip spaces + int ret = sscanf(searchPtr, "%d,%d", &socket, &length); + if (ret == 2) + { + if (_printDebug == true) + _debugPort->println(F("processReadEvent: read socket data")); + // From the UBX_CELL 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] == UBX_CELL_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; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerReadUDPSocket(const char *event) +{ + // URC: +UUSORF (Receive From command (UDP only)) + int socket, length; + char *searchPtr = strnstr(event, UBX_CELL_READ_UDP_SOCKET_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_READ_UDP_SOCKET_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + int ret = sscanf(searchPtr, "%d,%d", &socket, &length); + if (ret == 2) + { + if (_printDebug == true) + _debugPort->println(F("processReadEvent: UDP receive")); + parseSocketReadIndicationUDP(socket, length); + return true; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerListeningSocket(const char *event) +{ + // URC: +UUSOLI (Set Listening Socket) + int socket = 0; + int listenSocket = 0; + unsigned int port = 0; + unsigned int listenPort = 0; + IPAddress remoteIP = {0, 0, 0, 0}; + IPAddress localIP = {0, 0, 0, 0}; + int remoteIPstore[4] = {0, 0, 0, 0}; + int localIPstore[4] = {0, 0, 0, 0}; + + char *searchPtr = strnstr(event, UBX_CELL_LISTEN_SOCKET_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_LISTEN_SOCKET_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + int ret = sscanf(searchPtr, "%d,\"%d.%d.%d.%d\",%u,%d,\"%d.%d.%d.%d\",%u", &socket, &remoteIPstore[0], + &remoteIPstore[1], &remoteIPstore[2], &remoteIPstore[3], &port, &listenSocket, + &localIPstore[0], &localIPstore[1], &localIPstore[2], &localIPstore[3], &listenPort); + for (int i = 0; i <= 3; i++) + { + if (ret >= 5) + remoteIP[i] = (uint8_t)remoteIPstore[i]; + if (ret >= 11) + localIP[i] = (uint8_t)localIPstore[i]; + } + if (ret >= 5) + { + if (_printDebug == true) + _debugPort->println(F("processReadEvent: socket listen")); + parseSocketListenIndication(listenSocket, localIP, listenPort, socket, remoteIP, port); + return true; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerCloseSocket(const char *event) +{ + // URC: +UUSOCL (Close Socket) + int socket; + char *searchPtr = strnstr(event, UBX_CELL_CLOSE_SOCKET_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_CLOSE_SOCKET_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + int ret = sscanf(searchPtr, "%d", &socket); + if (ret == 1) + { + if (_printDebug == true) + _debugPort->println(F("processReadEvent: socket close")); + if ((socket >= 0) && (socket <= 6)) + { + if (_socketCloseCallback != nullptr) + { + _socketCloseCallback(socket); + } + } + return true; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerGNSSRequestLocation(const char *event) +{ + // URC: +UULOC (Localization information - CellLocate and hybrid positioning) + ClockData clck; + PositionData gps; + SpeedData spd; + unsigned long uncertainty; + int scanNum; + int latH, lonH, alt; + unsigned int speedU, cogU; + char latL[10], lonL[10]; + int dateStore[5]; + + // Maybe we should also scan for +UUGIND and extract the activated gnss system? + + // This assumes the ULOC response type is "0" or "1" - as selected by gpsRequest detailed + char *searchPtr = strnstr(event, UBX_CELL_GNSS_REQUEST_LOCATION_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_GNSS_REQUEST_LOCATION_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d/%d/%d,%d:%d:%d.%d,%d.%[^,],%d.%[^,],%d,%lu,%u,%u,%*s", &dateStore[0], + &dateStore[1], &clck.date.year, &dateStore[2], &dateStore[3], &dateStore[4], &clck.time.ms, + &latH, latL, &lonH, lonL, &alt, &uncertainty, &speedU, &cogU); + clck.date.day = dateStore[0]; + clck.date.month = dateStore[1]; + clck.time.hour = dateStore[2]; + clck.time.minute = dateStore[3]; + clck.time.second = dateStore[4]; + + if (scanNum >= 13) + { + // Found a Location string! + if (_printDebug == true) + { + _debugPort->println(F("processReadEvent: location")); + } + + if (latH >= 0) + gps.lat = (float)latH + ((float)atol(latL) / pow(10, strlen(latL))); + else + gps.lat = (float)latH - ((float)atol(latL) / pow(10, strlen(latL))); + if (lonH >= 0) + gps.lon = (float)lonH + ((float)atol(lonL) / pow(10, strlen(lonL))); + else + gps.lon = (float)lonH - ((float)atol(lonL) / pow(10, strlen(lonL))); + gps.alt = (float)alt; + if (scanNum >= 15) // If detailed response, get speed data + { + spd.speed = (float)speedU; + spd.cog = (float)cogU; + } + + // if (_printDebug == true) + // { + // _debugPort->print(F("processReadEvent: location: lat: ")); + // _debugPort->print(gps.lat, 7); + // _debugPort->print(F(" lon: ")); + // _debugPort->print(gps.lon, 7); + // _debugPort->print(F(" alt: ")); + // _debugPort->print(gps.alt, 2); + // _debugPort->print(F(" speed: ")); + // _debugPort->print(spd.speed, 2); + // _debugPort->print(F(" cog: ")); + // _debugPort->println(spd.cog, 2); + // } + + if (_gpsRequestCallback != nullptr) + { + _gpsRequestCallback(clck, gps, spd, uncertainty); + } + + return true; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerSIMState(const char *event) +{ + // URC: +UUSIMSTAT (SIM Status) + UBX_CELL_sim_states_t state; + int scanNum; + int stateStore; + + char *searchPtr = strnstr(event, UBX_CELL_SIM_STATE_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_SIM_STATE_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d", &stateStore); + + if (scanNum == 1) + { + if (_printDebug == true) + _debugPort->println(F("processReadEvent: SIM status")); + + state = (UBX_CELL_sim_states_t)stateStore; + + if (_simStateReportCallback != nullptr) + { + _simStateReportCallback(state); + } + + return true; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerHTTPCommand(const char *event) +{ + // URC: +UUHTTPCR (HTTP Command Result) + int profile, command, result; + int scanNum; + + char *searchPtr = strnstr(event, UBX_CELL_HTTP_COMMAND_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_HTTP_COMMAND_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,%d,%d", &profile, &command, &result); + + if (scanNum == 3) + { + if (_printDebug == true) + _debugPort->println(F("processReadEvent: HTTP command result")); + + if ((profile >= 0) && (profile < UBX_CELL_NUM_HTTP_PROFILES)) + { + if (_httpCommandRequestCallback != nullptr) + { + _httpCommandRequestCallback(profile, command, result); + } + } + + return true; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerMQTTCommand(const char *event) +{ + // URC: +UUMQTTC (MQTT Command Result) + int command, result; + int scanNum; + int qos = -1; + String topic; + + char *searchPtr = strnstr(event, UBX_CELL_MQTT_COMMAND_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_MQTT_COMMAND_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + { + searchPtr++; // skip spaces + } + + scanNum = sscanf(searchPtr, "%d,%d", &command, &result); + if ((scanNum == 2) && (command == UBX_CELL_MQTT_COMMAND_SUBSCRIBE)) + { + char topicC[100] = ""; + scanNum = sscanf(searchPtr, "%*d,%*d,%d,\"%[^\"]\"", &qos, topicC); + topic = topicC; + } + if ((scanNum == 2) || (scanNum == 4)) + { + if (_printDebug == true) + { + _debugPort->println(F("processReadEvent: MQTT command result")); + } + + if (_mqttCommandRequestCallback != nullptr) + { + _mqttCommandRequestCallback(command, result); + } + + return true; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerPingCommand(const char *event) +{ + // URC: +UUPING (Ping Result) + int retry = 0; + int p_size = 0; + int ttl = 0; + String remote_host = ""; + IPAddress remoteIP = {0, 0, 0, 0}; + long rtt = 0; + int scanNum; + + // Try to extract the UUPING retries and payload size + char *searchPtr = strnstr(event, UBX_CELL_PING_COMMAND_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_PING_COMMAND_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,%d,", &retry, &p_size); + + if (scanNum == 2) + { + if (_printDebug == true) + { + _debugPort->println(F("processReadEvent: ping")); + } + + searchPtr = strchr(++searchPtr, '\"'); // Search to the first quote + + // Extract the remote host name, stop at the next quote + while ((*(++searchPtr) != '\"') && (*searchPtr != '\0')) + { + remote_host.concat(*(searchPtr)); + } + + if (*searchPtr != '\0') // Make sure we found a quote + { + // Extract IP address + int remoteIPstore[4]; + scanNum = sscanf(searchPtr, "\",\"%d.%d.%d.%d", &remoteIPstore[0], &remoteIPstore[1], &remoteIPstore[2], + &remoteIPstore[3]); + for (int i = 0; i <= 3; i++) + { + remoteIP[i] = (uint8_t)remoteIPstore[i]; + } + + if (scanNum == 4) // Make sure we extracted enough data + { + // Extract TTL, should be immediately after IP address + searchPtr = strchr(searchPtr + 2, ','); // +2 to skip the quote and comma + if (searchPtr != nullptr) + { + // It's possible the TTL is not present (eg. on LARA-R6), so we + // can ignore scanNum since ttl defaults to 0 anyways + scanNum = sscanf(searchPtr, ",%d", &ttl); + + // Extract RTT, should be immediately after TTL + searchPtr = strchr(searchPtr + 1, ','); // +1 to skip the comma + if (searchPtr != nullptr) + { + scanNum = sscanf(searchPtr, ",%ld", &rtt); + + // Callback, if it exists + if (_pingRequestCallback != nullptr) + { + _pingRequestCallback(retry, p_size, remote_host, remoteIP, ttl, rtt); + } + } + } + } + } + return true; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerFTPCommand(const char *event) +{ + // URC: +UUFTPCR (FTP Command Result) + int ftpCmd; + int ftpResult; + int scanNum; + char *searchPtr = strnstr(event, UBX_CELL_FTP_COMMAND_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_FTP_COMMAND_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + { + searchPtr++; // skip spaces + } + + scanNum = sscanf(searchPtr, "%d,%d", &ftpCmd, &ftpResult); + if (scanNum == 2 && _ftpCommandRequestCallback != nullptr) + { + _ftpCommandRequestCallback(ftpCmd, ftpResult); + return true; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerRegistrationStatus(const char *event) +{ + // URC: +CREG + int status = 0; + unsigned int lac = 0, ci = 0, Act = 0; + char *searchPtr = strnstr(event, UBX_CELL_REGISTRATION_STATUS_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += strlen(UBX_CELL_REGISTRATION_STATUS_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + int scanNum = sscanf(searchPtr, "%d,\"%4x\",\"%4x\",%d", &status, &lac, &ci, &Act); + if (scanNum == 4) + { + if (_printDebug == true) + _debugPort->println(F("processReadEvent: CREG")); + + if (_registrationCallback != nullptr) + { + _registrationCallback((UBX_CELL_registration_status_t)status, lac, ci, Act); + } + + return true; + } + } + + return false; +} + +bool SparkFun_ublox_Cellular::urcHandlerEPSRegistrationStatus(const char *event) +{ + // URC: +CEREG + int status = 0; + unsigned int tac = 0, ci = 0, Act = 0; + char *searchPtr = strnstr(event, UBX_CELL_EPSREGISTRATION_STATUS_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + searchPtr += + strlen(UBX_CELL_EPSREGISTRATION_STATUS_URC); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + int scanNum = sscanf(searchPtr, "%d,\"%4x\",\"%4x\",%d", &status, &tac, &ci, &Act); + if (scanNum == 4) + { + if (_printDebug == true) + _debugPort->println(F("processReadEvent: CEREG")); + + if (_epsRegistrationCallback != nullptr) + { + _epsRegistrationCallback((UBX_CELL_registration_status_t)status, tac, ci, Act); + } + + return true; + } + } + + return false; +} + +void SparkFun_ublox_Cellular::addURCHandler(const char *urcString, UBX_CELL_urc_handler_t urcHandler) +{ + _urcStrings.push_back(urcString); + _urcHandlers.push_back(urcHandler); +} + +// Parse incoming URC's - the associated parse functions pass the data to the user via the callbacks (if defined) +bool SparkFun_ublox_Cellular::processURCEvent(const char *event) +{ + // Iterate through each URC handler to see if it can handle this message + for (auto urcHandler : _urcHandlers) + { + if (urcHandler(event)) + { + // This handler took care of it, so we're done! + return true; + } + } + + // None of the handlers took care of it + return false; +} + +// 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. +bool SparkFun_ublox_Cellular::poll(void) +{ + if (_pollReentrant == true) // Check for reentry (i.e. poll has been called from inside a callback) + return false; + + _pollReentrant = true; + + int avail = 0; + char c = 0; + bool handled = false; + + 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 + { + if (hwAvailable() > 0) // hwAvailable can return -1 if the serial port is NULL + { + c = readChar(); + _saraRXBuffer[avail++] = c; + } + else + { + yield(); + } + } + + // Now search for all supported URC's + handled = processURCEvent(_saraRXBuffer); + if (handled && (true == _printAtDebug)) + { + _debugAtPort->write(_saraRXBuffer, avail); + } + if ((handled == false) && (strlen(_saraRXBuffer) > 2)) + { + if (_printDebug == true) + { + _debugPort->print(F("poll: ")); + _debugPort->println(_saraRXBuffer); + } + } + } + + _pollReentrant = false; + + return handled; +} + +void SparkFun_ublox_Cellular::setSocketListenCallback(void (*socketListenCallback)(int, IPAddress, unsigned int, int, IPAddress, + unsigned int)) +{ + _socketListenCallback = socketListenCallback; +} + +void SparkFun_ublox_Cellular::setSocketReadCallback(void (*socketReadCallback)(int, String)) +{ + _socketReadCallback = socketReadCallback; +} + +void SparkFun_ublox_Cellular::setSocketReadCallbackPlus(void (*socketReadCallbackPlus)( + int, const char *, int, IPAddress, int)) // socket, data, length, remoteAddress, remotePort +{ + _socketReadCallbackPlus = socketReadCallbackPlus; +} + +void SparkFun_ublox_Cellular::setSocketCloseCallback(void (*socketCloseCallback)(int)) +{ + _socketCloseCallback = socketCloseCallback; +} + +void SparkFun_ublox_Cellular::setGpsReadCallback(void (*gpsRequestCallback)(ClockData time, PositionData gps, SpeedData spd, + unsigned long uncertainty)) +{ + _gpsRequestCallback = gpsRequestCallback; +} + +void SparkFun_ublox_Cellular::setSIMstateReportCallback(void (*simStateReportCallback)(UBX_CELL_sim_states_t state)) +{ + _simStateReportCallback = simStateReportCallback; +} + +void SparkFun_ublox_Cellular::setPSDActionCallback(void (*psdActionRequestCallback)(int result, IPAddress ip)) +{ + _psdActionRequestCallback = psdActionRequestCallback; +} + +void SparkFun_ublox_Cellular::setPingCallback(void (*pingRequestCallback)(int retry, int p_size, String remote_hostname, IPAddress ip, + int ttl, long rtt)) +{ + _pingRequestCallback = pingRequestCallback; +} + +void SparkFun_ublox_Cellular::setHTTPCommandCallback(void (*httpCommandRequestCallback)(int profile, int command, int result)) +{ + _httpCommandRequestCallback = httpCommandRequestCallback; +} + +void SparkFun_ublox_Cellular::setMQTTCommandCallback(void (*mqttCommandRequestCallback)(int command, int result)) +{ + _mqttCommandRequestCallback = mqttCommandRequestCallback; +} + +void SparkFun_ublox_Cellular::setFTPCommandCallback(void (*ftpCommandRequestCallback)(int command, int result)) +{ + _ftpCommandRequestCallback = ftpCommandRequestCallback; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setRegistrationCallback(void (*registrationCallback)(UBX_CELL_registration_status_t status, + unsigned int lac, unsigned int ci, + int Act)) +{ + _registrationCallback = registrationCallback; + + size_t cmdLen = strlen(UBX_CELL_REGISTRATION_STATUS) + 3; + char command[cmdLen]; + snprintf(command, cmdLen, "%s=%d", UBX_CELL_REGISTRATION_STATUS, 2 /*enable URC with location*/); + UBX_CELL_error_t err = + sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setEpsRegistrationCallback( + void (*registrationCallback)(UBX_CELL_registration_status_t status, unsigned int tac, unsigned int ci, int Act)) +{ + _epsRegistrationCallback = registrationCallback; + + size_t cmdLen = strlen(UBX_CELL_EPSREGISTRATION_STATUS) + 3; + char command[cmdLen]; + snprintf(command, cmdLen, "%s=%d", UBX_CELL_EPSREGISTRATION_STATUS, 2 /*enable URC with location*/); + UBX_CELL_error_t err = + sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +size_t SparkFun_ublox_Cellular::write(uint8_t c) +{ + return hwWrite(c); +} + +size_t SparkFun_ublox_Cellular::write(const char *str) +{ + return hwPrint(str); +} + +size_t SparkFun_ublox_Cellular::write(const char *buffer, size_t size) +{ + return hwWriteData(buffer, size); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::at(void) +{ + UBX_CELL_error_t err; + + err = sendCommandWithResponse(nullptr, UBX_CELL_RESPONSE_OK, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::enableEcho(bool enable) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_ECHO) + 2; + char command[cmdLen]; + snprintf(command, cmdLen, "%s%d", UBX_CELL_COMMAND_ECHO, enable ? 1 : 0); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +String SparkFun_ublox_Cellular::getManufacturerID(void) +{ + char response[minimumResponseAllocation]; + char idResponse[16] = {0x00}; // E.g. u-blox + UBX_CELL_error_t err; + + err = sendCommandWithResponse(UBX_CELL_COMMAND_MANU_ID, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + if (sscanf(response, "\r\n%15s\r\n", idResponse) != 1) + { + memset(idResponse, 0, 16); + } + } + return String(idResponse); +} + +String SparkFun_ublox_Cellular::getModelID(void) +{ + char response[minimumResponseAllocation]; + char idResponse[32] = {0x00}; // E.g. SARA-R510M8Q + UBX_CELL_error_t err; + + err = sendCommandWithResponse(UBX_CELL_COMMAND_MODEL_ID, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + if (sscanf(response, "\r\n%31s\r\n", idResponse) != 1) + { + memset(idResponse, 0, 16); + } + } + return String(idResponse); +} + +String SparkFun_ublox_Cellular::getFirmwareVersion(void) +{ + char response[minimumResponseAllocation]; + char idResponse[16] = {0x00}; // E.g. 11.40 + UBX_CELL_error_t err; + + err = sendCommandWithResponse(UBX_CELL_COMMAND_FW_VER_ID, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + if (sscanf(response, "\r\n%15s\r\n", idResponse) != 1) + { + memset(idResponse, 0, 16); + } + } + return String(idResponse); +} + +String SparkFun_ublox_Cellular::getSerialNo(void) +{ + char response[minimumResponseAllocation]; + char idResponse[32] = {0x00}; // E.g. 357520070120767 + UBX_CELL_error_t err; + + err = sendCommandWithResponse(UBX_CELL_COMMAND_SERIAL_NO, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + if (sscanf(response, "\r\n%31s\r\n", idResponse) != 1) + { + memset(idResponse, 0, 16); + } + } + return String(idResponse); +} + +String SparkFun_ublox_Cellular::getIMEI(void) +{ + char response[minimumResponseAllocation]; + char imeiResponse[32] = {0x00}; // E.g. 004999010640000 + UBX_CELL_error_t err; + + err = sendCommandWithResponse(UBX_CELL_COMMAND_IMEI, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + if (sscanf(response, "\r\n%31s\r\n", imeiResponse) != 1) + { + memset(imeiResponse, 0, 16); + } + } + return String(imeiResponse); +} + +String SparkFun_ublox_Cellular::getIMSI(void) +{ + char response[minimumResponseAllocation]; + char imsiResponse[32] = {0x00}; // E.g. 222107701772423 + UBX_CELL_error_t err; + + err = sendCommandWithResponse(UBX_CELL_COMMAND_IMSI, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + if (sscanf(response, "\r\n%31s\r\n", imsiResponse) != 1) + { + memset(imsiResponse, 0, 16); + } + } + return String(imsiResponse); +} + +String SparkFun_ublox_Cellular::getCCID(void) +{ + char response[minimumResponseAllocation]; + char ccidResponse[32] = {0x00}; // E.g. +CCID: 8939107900010087330 + UBX_CELL_error_t err; + + err = sendCommandWithResponse(UBX_CELL_COMMAND_CCID, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "\r\n+CCID:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("\r\n+CCID:"); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + if (sscanf(searchPtr, "%31s", ccidResponse) != 1) + { + ccidResponse[0] = 0; + } + } + } + return String(ccidResponse); +} + +String SparkFun_ublox_Cellular::getSubscriberNo(void) +{ + char response[minimumResponseAllocation]; + char idResponse[128] = {0x00}; // E.g. +CNUM: "ABCD . AAA","123456789012",129 + UBX_CELL_error_t err; + + err = sendCommandWithResponse(UBX_CELL_COMMAND_CNUM, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_10_SEC_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "\r\n+CNUM:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("\r\n+CNUM:"); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + if (sscanf(searchPtr, "%127s", idResponse) != 1) + { + idResponse[0] = 0; + } + } + } + return String(idResponse); +} + +String SparkFun_ublox_Cellular::getCapabilities(void) +{ + char response[minimumResponseAllocation]; + char idResponse[128] = {0x00}; // E.g. +GCAP: +FCLASS, +CGSM + UBX_CELL_error_t err; + + err = sendCommandWithResponse(UBX_CELL_COMMAND_REQ_CAP, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "\r\n+GCAP:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("\r\n+GCAP:"); // Move searchPtr to first character - probably a space + while (*searchPtr == ' ') + searchPtr++; // skip spaces + if (sscanf(searchPtr, "%127s", idResponse) != 1) + { + idResponse[0] = 0; + } + } + } + return String(idResponse); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::reset(void) +{ + UBX_CELL_error_t err; + + err = functionality(SILENT_RESET_WITH_SIM); + if (err == UBX_CELL_ERROR_SUCCESS) + { + // Reset will set the baud rate back to 115200 + // beginSerial(9600); + err = UBX_CELL_ERROR_INVALID; + while (err != UBX_CELL_ERROR_SUCCESS) + { + beginSerial(UBX_CELL_DEFAULT_BAUD_RATE); + setBaud(_baud); + beginSerial(_baud); + err = at(); + } + return init(_baud); + } + return err; +} + +String SparkFun_ublox_Cellular::clock(void) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_CLOCK) + 2; + char command[cmdLen]; + char response[minimumResponseAllocation]; + char *clockBegin; + char *clockEnd; + + snprintf(command, cmdLen, "%s?", UBX_CELL_COMMAND_CLOCK); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err != UBX_CELL_ERROR_SUCCESS) + { + return ""; + } + + // Response format: \r\n+CCLK: "YY/MM/DD,HH:MM:SS-TZ"\r\n\r\nOK\r\n + clockBegin = strchr(response, '\"'); // Find first quote + if (clockBegin == nullptr) + { + return ""; + } + clockBegin += 1; // Increment pointer to begin at first number + clockEnd = strchr(clockBegin, '\"'); // Find last quote + if (clockEnd == nullptr) + { + return ""; + } + *(clockEnd) = '\0'; // Set last quote to null char -- end string + + String clock = String(clockBegin); // Extract the clock as a String _before_ freeing response + + return (clock); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::clock(uint8_t *y, uint8_t *mo, uint8_t *d, uint8_t *h, uint8_t *min, uint8_t *s, int8_t *tz) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_CLOCK) + 2; + char command[cmdLen]; + char response[minimumResponseAllocation]; + char tzPlusMinus; + int scanNum = 0; + + int iy, imo, id, ih, imin, is, itz; + + snprintf(command, cmdLen, "%s?", UBX_CELL_COMMAND_CLOCK); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + // Response format (if TZ is negative): \r\n+CCLK: "YY/MM/DD,HH:MM:SS-TZ"\r\n\r\nOK\r\n + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+CCLK:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+CCLK:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = + sscanf(searchPtr, "\"%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; + *d = id; + *h = ih; + *min = imin; + *s = is; + if (tzPlusMinus == '-') + *tz = 0 - itz; + else + *tz = itz; + } + else + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setClock(uint8_t y, uint8_t mo, uint8_t d, uint8_t h, uint8_t min, uint8_t s, int8_t tz) +{ + // Convert y,mo,d,h,min,s,tz into a String + // Some platforms don't support snprintf correctly (for %02d or %+02d) so we need to build the String manually + // Format is "yy/MM/dd,hh:mm:ss+TZ" + // TZ can be +/- and is in increments of 15 minutes (not hours) + + String theTime = ""; + + theTime.concat(y / 10); + theTime.concat(y % 10); + theTime.concat('/'); + theTime.concat(mo / 10); + theTime.concat(mo % 10); + theTime.concat('/'); + theTime.concat(d / 10); + theTime.concat(d % 10); + theTime.concat(','); + theTime.concat(h / 10); + theTime.concat(h % 10); + theTime.concat(':'); + theTime.concat(min / 10); + theTime.concat(min % 10); + theTime.concat(':'); + theTime.concat(s / 10); + theTime.concat(s % 10); + if (tz < 0) + { + theTime.concat('-'); + tz = 0 - tz; + } + else + theTime.concat('+'); + theTime.concat(tz / 10); + theTime.concat(tz % 10); + + return (setClock(theTime)); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setClock(String theTime) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_CLOCK) + theTime.length() + 8; + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=\"%s\"", UBX_CELL_COMMAND_CLOCK, theTime.c_str()); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +void SparkFun_ublox_Cellular::autoTimeZoneForBegin(bool tz) +{ + _autoTimeZoneForBegin = tz; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::autoTimeZone(bool enable) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_AUTO_TZ) + 3; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_COMMAND_AUTO_TZ, enable ? 1 : 0); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +int8_t SparkFun_ublox_Cellular::rssi(void) +{ + size_t cmdLen = strlen(UBX_CELL_SIGNAL_QUALITY) + 1; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int rssi; + + snprintf(command, cmdLen, "%s", UBX_CELL_SIGNAL_QUALITY); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, 10000, minimumResponseAllocation, + AT_COMMAND); + if (err != UBX_CELL_ERROR_SUCCESS) + { + return -1; + } + + int scanned = 0; + char *searchPtr = strnstr(response, "+CSQ:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+CSQ:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%d,%*d", &rssi); + } + if (scanned != 1) + { + rssi = -1; + } + + return rssi; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getExtSignalQuality(signal_quality &signal_quality) +{ + size_t cmdLen = strlen(UBX_CELL_EXT_SIGNAL_QUALITY) + 1; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + + snprintf(command, cmdLen, "%s", UBX_CELL_EXT_SIGNAL_QUALITY); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, 10000, minimumResponseAllocation, + AT_COMMAND); + if (err != UBX_CELL_ERROR_SUCCESS) + { + return UBX_CELL_ERROR_ERROR; + } + + int scanned = 0; + const char *responseStr = "+CESQ:"; + char *searchPtr = strnstr(response, responseStr, minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen(responseStr); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%u,%u,%u,%u,%u,%u", &signal_quality.rxlev, &signal_quality.ber, + &signal_quality.rscp, &signal_quality.enc0, &signal_quality.rsrq, &signal_quality.rsrp); + } + + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + if (scanned == 6) + { + err = UBX_CELL_ERROR_SUCCESS; + } + + return err; +} + +UBX_CELL_registration_status_t SparkFun_ublox_Cellular::registration(bool eps) +{ + const char *tag = eps ? UBX_CELL_EPSREGISTRATION_STATUS : UBX_CELL_REGISTRATION_STATUS; + size_t cmdLen = strlen(UBX_CELL_EPSREGISTRATION_STATUS) + 3; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int status; + + snprintf(command, cmdLen, "%s?", tag); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT, + minimumResponseAllocation, AT_COMMAND); + if (err != UBX_CELL_ERROR_SUCCESS) + { + return UBX_CELL_REGISTRATION_INVALID; + } + + int scanned = 0; + const char *startTag = eps ? UBX_CELL_EPSREGISTRATION_STATUS_URC : UBX_CELL_REGISTRATION_STATUS_URC; + char *searchPtr = strnstr(response, startTag, minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += eps ? strlen(UBX_CELL_EPSREGISTRATION_STATUS_URC) + : strlen(UBX_CELL_REGISTRATION_STATUS_URC); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%*d,%d", &status); + } + if (scanned != 1) + status = UBX_CELL_REGISTRATION_INVALID; + + return (UBX_CELL_registration_status_t)status; +} + +bool SparkFun_ublox_Cellular::setNetworkProfile(mobile_network_operator_t mno, bool autoReset, bool urcNotification) +{ + mobile_network_operator_t currentMno; + + // Check currently set MNO profile + if (getMNOprofile(¤tMno) != UBX_CELL_ERROR_SUCCESS) + { + return false; + } + + if (currentMno == mno) + { + return true; + } + + // Disable transmit and receive so we can change operator + if (functionality(MINIMUM_FUNCTIONALITY) != UBX_CELL_ERROR_SUCCESS) + { + return false; + } + + if (setMNOprofile(mno, autoReset, urcNotification) != UBX_CELL_ERROR_SUCCESS) + { + return false; + } + + if (reset() != UBX_CELL_ERROR_SUCCESS) + { + return false; + } + + return true; +} + +mobile_network_operator_t SparkFun_ublox_Cellular::getNetworkProfile(void) +{ + mobile_network_operator_t mno; + UBX_CELL_error_t err; + + err = getMNOprofile(&mno); + if (err != UBX_CELL_ERROR_SUCCESS) + { + return MNO_INVALID; + } + return mno; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setAPN(String apn, uint8_t cid, UBX_CELL_pdp_type pdpType) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MESSAGE_PDP_DEF) + strlen(apn.c_str()) + 16; + char *command; + char pdpStr[8]; + + memset(pdpStr, 0, 8); + + if (cid >= 8) + return UBX_CELL_ERROR_UNEXPECTED_PARAM; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + switch (pdpType) + { + case PDP_TYPE_INVALID: + free(command); + return UBX_CELL_ERROR_UNEXPECTED_PARAM; + break; + case PDP_TYPE_IP: + memcpy(pdpStr, "IP", 2); + break; + case PDP_TYPE_NONIP: + memcpy(pdpStr, "NONIP", 5); + break; + case PDP_TYPE_IPV4V6: + memcpy(pdpStr, "IPV4V6", 6); + break; + case PDP_TYPE_IPV6: + memcpy(pdpStr, "IPV6", 4); + break; + default: + free(command); + return UBX_CELL_ERROR_UNEXPECTED_PARAM; + break; + } + if (apn == nullptr) + { + if (_printDebug == true) + _debugPort->println(F("setAPN: nullptr")); + snprintf(command, cmdLen, "%s=%d,\"%s\",\"\"", UBX_CELL_MESSAGE_PDP_DEF, cid, pdpStr); + } + else + { + if (_printDebug == true) + { + _debugPort->print(F("setAPN: ")); + _debugPort->println(apn); + } + snprintf(command, cmdLen, "%s=%d,\"%s\",\"%s\"", UBX_CELL_MESSAGE_PDP_DEF, cid, pdpStr, apn.c_str()); + } + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + + return err; +} + +// Return the Access Point Name and IP address for the chosen context identifier +UBX_CELL_error_t SparkFun_ublox_Cellular::getAPN(int cid, String *apn, IPAddress *ip, UBX_CELL_pdp_type *pdpType) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MESSAGE_PDP_DEF) + 3; + char command[cmdLen]; + char response[1024]; + + if (cid > UBX_CELL_NUM_PDP_CONTEXT_IDENTIFIERS) + return UBX_CELL_ERROR_ERROR; + + snprintf(command, cmdLen, "%s?", UBX_CELL_MESSAGE_PDP_DEF); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT, + 1024); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + // 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 + int rcid = -1; + char *searchPtr = response; + + bool keepGoing = true; + while (keepGoing == true) + { + int scanned = 0; + // Find the first/next occurrence of +CGDCONT: + searchPtr = strnstr(searchPtr, "+CGDCONT:", 1024 - (searchPtr - response)); + if (searchPtr != nullptr) + { + char strPdpType[10]; + char strApn[128]; + int ipOct[4]; + + searchPtr += strlen("+CGDCONT:"); // Point to the cid + 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 ((scanned == 7) && (rcid == cid)) + { + if (apn) + *apn = strApn; + for (int o = 0; ip && (o < 4); o++) + { + (*ip)[o] = (uint8_t)ipOct[o]; + } + if (pdpType) + { + *pdpType = (0 == strcmp(strPdpType, "IPV4V6")) ? PDP_TYPE_IPV4V6 + : (0 == strcmp(strPdpType, "IPV6")) ? PDP_TYPE_IPV6 + : (0 == strcmp(strPdpType, "IP")) ? PDP_TYPE_IP + : PDP_TYPE_INVALID; + } + keepGoing = false; + } + } + else // We don't have a match so let's clear the APN and IP address + { + if (apn) + *apn = ""; + if (pdpType) + *pdpType = PDP_TYPE_INVALID; + if (ip) + *ip = {0, 0, 0, 0}; + keepGoing = false; + } + } + } + else + { + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getSimStatus(String *code) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_SIMPIN) + 2; + char command[cmdLen]; + char response[minimumResponseAllocation]; + + snprintf(command, cmdLen, "%s?", UBX_CELL_COMMAND_SIMPIN); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + int scanned = 0; + char c[16]; + char *searchPtr = strnstr(response, "+CPIN:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+CPIN:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%15s\r\n", c); + } + if (scanned == 1) + { + if (code) + *code = c; + } + else + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setSimPin(String pin) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_SIMPIN) + 4 + pin.length(); + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=\"%s\"", UBX_CELL_COMMAND_SIMPIN, pin.c_str()); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setSIMstateReportingMode(int mode) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_SIM_STATE) + 4; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_SIM_STATE, mode); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getSIMstateReportingMode(int *mode) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_SIM_STATE) + 3; + char command[cmdLen]; + char response[minimumResponseAllocation]; + + int m; + + snprintf(command, cmdLen, "%s?", UBX_CELL_SIM_STATE); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + int scanned = 0; + char *searchPtr = strnstr(response, "+USIMSTAT:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USIMSTAT:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%d\r\n", &m); + } + if (scanned == 1) + { + *mode = m; + } + else + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + return err; +} + +const char *PPP_L2P[5] = { + "", "PPP", "M-HEX", "M-RAW_IP", "M-OPT-PPP", +}; + +UBX_CELL_error_t SparkFun_ublox_Cellular::enterPPP(uint8_t cid, char dialing_type_char, unsigned long dialNumber, + SparkFun_ublox_Cellular::UBX_CELL_l2p_t l2p) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MESSAGE_ENTER_PPP) + 32; + char command[cmdLen]; + + if ((dialing_type_char != 0) && (dialing_type_char != 'T') && (dialing_type_char != 'P')) + { + return UBX_CELL_ERROR_UNEXPECTED_PARAM; + } + + if (dialing_type_char != 0) + { + snprintf(command, cmdLen, "%s%c*%lu**%s*%u#", UBX_CELL_MESSAGE_ENTER_PPP, dialing_type_char, dialNumber, + PPP_L2P[l2p], (unsigned int)cid); + } + else + { + snprintf(command, cmdLen, "%s*%lu**%s*%u#", UBX_CELL_MESSAGE_ENTER_PPP, dialNumber, PPP_L2P[l2p], + (unsigned int)cid); + } + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_CONNECT, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +uint8_t SparkFun_ublox_Cellular::getOperators(struct operator_stats *opRet, int maxOps) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_OPERATOR_SELECTION) + 3; + char command[cmdLen]; + char *response; + uint8_t opsSeen = 0; + + snprintf(command, cmdLen, "%s=?", UBX_CELL_OPERATOR_SELECTION); + + int responseSize = (maxOps + 1) * 48; + response = ubx_cell_calloc_char(responseSize); + if (response == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + + // AT+COPS maximum response time is 3 minutes (180000 ms) + err = + sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_3_MIN_TIMEOUT, responseSize); + + // Sample responses: + // +COPS: (3,"Verizon Wireless","VzW","311480",8),,(0,1,2,3,4),(0,1,2) + // +COPS: (1,"313 100","313 100","313100",8),(2,"AT&T","AT&T","310410",8),(3,"311 480","311 + // 480","311480",8),,(0,1,2,3,4),(0,1,2) + + if (_printDebug == true) + { + _debugPort->print(F("getOperators: Response: {")); + _debugPort->print(response); + _debugPort->println(F("}")); + } + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *opBegin; + char *opEnd; + int op = 0; + int stat; + char longOp[26]; + char shortOp[11]; + int act; + unsigned long numOp; + + opBegin = response; + + for (; op < maxOps; op++) + { + opBegin = strchr(opBegin, '('); + if (opBegin == nullptr) + break; + opEnd = strchr(opBegin, ')'); + if (opEnd == nullptr) + break; + + int sscanRead = + sscanf(opBegin, "(%d,\"%[^\"]\",\"%[^\"]\",\"%lu\",%d)%*s", &stat, longOp, shortOp, &numOp, &act); + if (sscanRead == 5) + { + opRet[op].stat = stat; + opRet[op].longOp = (String)(longOp); + opRet[op].shortOp = (String)(shortOp); + opRet[op].numOp = numOp; + opRet[op].act = act; + opsSeen += 1; + } + // TODO: Search for other possible patterns here + else + { + break; // Break out if pattern doesn't match. + } + opBegin = opEnd + 1; // Move opBegin to beginning of next value + } + } + + free(response); + + return opsSeen; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::registerOperator(struct operator_stats oper) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_OPERATOR_SELECTION) + 24; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=1,2,\"%lu\"", UBX_CELL_OPERATOR_SELECTION, oper.numOp); + + // AT+COPS maximum response time is 3 minutes (180000 ms) + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_3_MIN_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::automaticOperatorSelection() +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_OPERATOR_SELECTION) + 6; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=0,0", UBX_CELL_OPERATOR_SELECTION); + + // AT+COPS maximum response time is 3 minutes (180000 ms) + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_3_MIN_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getOperator(String *oper) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_OPERATOR_SELECTION) + 3; + char command[cmdLen]; + char response[minimumResponseAllocation]; + char *searchPtr; + char mode; + + snprintf(command, cmdLen, "%s?", UBX_CELL_OPERATOR_SELECTION); + + // AT+COPS maximum response time is 3 minutes (180000 ms) + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_3_MIN_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + searchPtr = strnstr(response, "+COPS:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+COPS:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + mode = *searchPtr; // Read first char -- should be mode + if (mode == '2') // Check for de-register + { + err = UBX_CELL_ERROR_DEREGISTERED; + } + // Otherwise if it's default, manual, set-only, or automatic + else if ((mode == '0') || (mode == '1') || (mode == '3') || (mode == '4')) + { + *oper = ""; + searchPtr = strchr(searchPtr, '\"'); // Move to first quote + if (searchPtr == nullptr) + { + err = UBX_CELL_ERROR_DEREGISTERED; + } + else + { + while ((*(++searchPtr) != '\"') && (*searchPtr != '\0')) + { + oper->concat(*(searchPtr)); + } + } + if (_printDebug == true) + { + _debugPort->print(F("getOperator: ")); + _debugPort->println(*oper); + } + } + } + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::deregisterOperator(void) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_OPERATOR_SELECTION) + 6; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=2", UBX_CELL_OPERATOR_SELECTION); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_3_MIN_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setSMSMessageFormat(UBX_CELL_message_format_t textMode) +{ + size_t cmdLen = strlen(UBX_CELL_MESSAGE_FORMAT) + 4; + char command[cmdLen]; + UBX_CELL_error_t err; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_MESSAGE_FORMAT, (textMode == UBX_CELL_MESSAGE_FORMAT_TEXT) ? 1 : 0); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::sendSMS(String number, String message) +{ + char *command; + char *messageCStr; + char *numberCStr; + UBX_CELL_error_t err; + + numberCStr = ubx_cell_calloc_char(number.length() + 2); + if (numberCStr == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + number.toCharArray(numberCStr, number.length() + 1); + + size_t cmdLen = strlen(UBX_CELL_SEND_TEXT) + strlen(numberCStr) + 8; + command = ubx_cell_calloc_char(cmdLen); + if (command != nullptr) + { + snprintf(command, cmdLen, "%s=\"%s\"", UBX_CELL_SEND_TEXT, numberCStr); + + err = sendCommandWithResponse(command, ">", nullptr, UBX_CELL_10_SEC_TIMEOUT); + free(command); + free(numberCStr); + if (err != UBX_CELL_ERROR_SUCCESS) + return err; + + messageCStr = ubx_cell_calloc_char(message.length() + 1); + if (messageCStr == nullptr) + { + hwWrite(ASCII_CTRL_Z); + return UBX_CELL_ERROR_OUT_OF_MEMORY; + } + message.toCharArray(messageCStr, message.length() + 1); + messageCStr[message.length()] = ASCII_CTRL_Z; + + err = sendCommandWithResponse(messageCStr, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_10_SEC_TIMEOUT, + minimumResponseAllocation, NOT_AT_COMMAND); + + free(messageCStr); + } + else + { + free(numberCStr); + err = UBX_CELL_ERROR_OUT_OF_MEMORY; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getPreferredMessageStorage(int *used, int *total, String memory) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_PREF_MESSAGE_STORE) + 32; + char command[cmdLen]; + char response[minimumResponseAllocation]; + int u; + int t; + + snprintf(command, cmdLen, "%s=\"%s\"", UBX_CELL_PREF_MESSAGE_STORE, memory.c_str()); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_3_MIN_TIMEOUT); + + if (err != UBX_CELL_ERROR_SUCCESS) + { + return err; + } + + int scanned = 0; + char *searchPtr = strnstr(response, "+CPMS:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+CPMS:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%d,%d", &u, &t); + } + if (scanned == 2) + { + if (_printDebug == true) + { + _debugPort->print(F("getPreferredMessageStorage: memory1 (read and delete): ")); + _debugPort->print(memory); + _debugPort->print(F(" used: ")); + _debugPort->print(u); + _debugPort->print(F(" total: ")); + _debugPort->println(t); + } + *used = u; + *total = t; + } + else + { + err = UBX_CELL_ERROR_INVALID; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::readSMSmessage(int location, String *unread, String *from, String *dateTime, String *message) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_READ_TEXT_MESSAGE) + 5; + char command[cmdLen]; + char response[1024]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_READ_TEXT_MESSAGE, location); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_10_SEC_TIMEOUT, 1024); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = response; + + // Find the first occurrence of +CMGR: + searchPtr = strnstr(searchPtr, "+CMGR:", 1024 - (searchPtr - response)); + if (searchPtr != nullptr) + { + searchPtr += strlen("+CMGR:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + int pointer = 0; + while ((*(++searchPtr) != '\"') && (*searchPtr != '\0') && (pointer < 12)) + { + unread->concat(*(searchPtr)); + pointer++; + } + if ((*searchPtr == '\0') || (pointer == 12)) + { + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + // Search to the next quote + searchPtr = strchr(++searchPtr, '\"'); + pointer = 0; + while ((*(++searchPtr) != '\"') && (*searchPtr != '\0') && (pointer < 24)) + { + from->concat(*(searchPtr)); + pointer++; + } + if ((*searchPtr == '\0') || (pointer == 24)) + { + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + // Skip two commas + searchPtr = strchr(++searchPtr, ','); + searchPtr = strchr(++searchPtr, ','); + // Search to the next quote + searchPtr = strchr(++searchPtr, '\"'); + pointer = 0; + while ((*(++searchPtr) != '\"') && (*searchPtr != '\0') && (pointer < 24)) + { + dateTime->concat(*(searchPtr)); + pointer++; + } + if ((*searchPtr == '\0') || (pointer == 24)) + { + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + // Search to the next new line + searchPtr = strchr(++searchPtr, '\n'); + pointer = 0; + while ((*(++searchPtr) != '\r') && (*searchPtr != '\n') && (*searchPtr != '\0') && (pointer < 512)) + { + message->concat(*(searchPtr)); + pointer++; + } + if ((*searchPtr == '\0') || (pointer == 512)) + { + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + } + else + { + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + } + else + { + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::deleteSMSmessage(int location, int deleteFlag) +{ + size_t cmdLen = strlen(UBX_CELL_DELETE_MESSAGE) + 12; + char command[cmdLen]; + UBX_CELL_error_t err; + + if (deleteFlag == 0) + snprintf(command, cmdLen, "%s=%d", UBX_CELL_DELETE_MESSAGE, location); + else + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_DELETE_MESSAGE, location, deleteFlag); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_55_SECS_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setBaud(unsigned long baud) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_BAUD) + 7 + 12; + char command[cmdLen]; + int b = 0; + + // Error check -- ensure supported baud + for (; b < NUM_SUPPORTED_BAUD; b++) + { + if (UBX_CELL_SUPPORTED_BAUD[b] == baud) + { + break; + } + } + if (b >= NUM_SUPPORTED_BAUD) + { + return UBX_CELL_ERROR_UNEXPECTED_PARAM; + } + + // Construct command + snprintf(command, cmdLen, "%s=%lu", UBX_CELL_COMMAND_BAUD, baud); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_SET_BAUD_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setFlowControl(UBX_CELL_flow_control_t value) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_FLOW_CONTROL) + 16; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s%d", UBX_CELL_FLOW_CONTROL, value); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setGpioMode(UBX_CELL_gpio_t gpio, UBX_CELL_gpio_mode_t mode, int value) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_GPIO) + 16; + char command[cmdLen]; + + // Example command: AT+UGPIOC=16,2 + // Example command: AT+UGPIOC=23,0,1 + if (mode == GPIO_OUTPUT) + snprintf(command, cmdLen, "%s=%d,%d,%d", UBX_CELL_COMMAND_GPIO, gpio, mode, value); + else + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_COMMAND_GPIO, gpio, mode); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_10_SEC_TIMEOUT); + + return err; +} + +SparkFun_ublox_Cellular::UBX_CELL_gpio_mode_t SparkFun_ublox_Cellular::getGpioMode(UBX_CELL_gpio_t gpio) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_GPIO) + 2; + char command[cmdLen]; + char response[minimumResponseAllocation]; + size_t charLen = 4; + char gpioChar[charLen]; + char *gpioStart; + int gpioMode; + + snprintf(command, cmdLen, "%s?", UBX_CELL_COMMAND_GPIO); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err != UBX_CELL_ERROR_SUCCESS) + { + return GPIO_MODE_INVALID; + } + + snprintf(gpioChar, charLen, "%d", gpio); // Convert GPIO to char array + gpioStart = strnstr(response, gpioChar, minimumResponseAllocation); // Find first occurence of GPIO in response + + if (gpioStart == nullptr) + return GPIO_MODE_INVALID; // If not found return invalid + sscanf(gpioStart, "%*d,%d\r\n", &gpioMode); + + return (UBX_CELL_gpio_mode_t)gpioMode; +} + +int SparkFun_ublox_Cellular::socketOpen(UBX_CELL_socket_protocol_t protocol, unsigned int localPort) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_CREATE_SOCKET) + 10; + char command[cmdLen]; + char response[minimumResponseAllocation]; + int sockId = -1; + char *responseStart; + + if (localPort == 0) + snprintf(command, cmdLen, "%s=%d", UBX_CELL_CREATE_SOCKET, (int)protocol); + else + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_CREATE_SOCKET, (int)protocol, localPort); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("socketOpen: Fail: Error: ")); + _debugPort->print(err); + _debugPort->print(F(" Response: {")); + _debugPort->print(response); + _debugPort->println(F("}")); + } + return -1; + } + + responseStart = strnstr(response, "+USOCR:", minimumResponseAllocation); + if (responseStart == nullptr) + { + if (_printDebug == true) + { + _debugPort->print(F("socketOpen: Failure: {")); + _debugPort->print(response); + _debugPort->println(F("}")); + } + return -1; + } + + responseStart += strlen("+USOCR:"); // Move searchPtr to first char + while (*responseStart == ' ') + responseStart++; // skip spaces + sscanf(responseStart, "%d", &sockId); + _lastSocketProtocol[sockId] = (int)protocol; + + return sockId; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketClose(int socket, unsigned long timeout) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_CLOSE_SOCKET) + 10; + char command[cmdLen]; + char response[minimumResponseAllocation]; + + // if timeout is short, close asynchronously and don't wait for socket closure (we will get the URC later) + // this will make sure the AT command parser is not confused during init() + const char *format = (UBX_CELL_STANDARD_RESPONSE_TIMEOUT == timeout) ? "%s=%d,1" : "%s=%d"; + snprintf(command, cmdLen, format, UBX_CELL_CLOSE_SOCKET, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, timeout); + + if ((err != UBX_CELL_ERROR_SUCCESS) && (_printDebug == true)) + { + _debugPort->print(F("socketClose: Error: ")); + _debugPort->println(socketGetLastError()); + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketConnect(int socket, const char *address, unsigned int port) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_CONNECT_SOCKET) + strlen(address) + 11; + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,\"%s\",%d", UBX_CELL_CONNECT_SOCKET, socket, address, port); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_IP_CONNECT_TIMEOUT); + + free(command); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketConnect(int socket, IPAddress address, unsigned int port) +{ + size_t charLen = 16; + char charAddress[charLen]; + memset(charAddress, 0, 16); + snprintf(charAddress, charLen, "%d.%d.%d.%d", address[0], address[1], address[2], address[3]); + + return (socketConnect(socket, (const char *)charAddress, port)); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketWrite(int socket, const char *str, int len) +{ + size_t cmdLen = strlen(UBX_CELL_WRITE_SOCKET) + 16; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + + int dataLen = len == -1 ? strlen(str) : len; + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_WRITE_SOCKET, socket, dataLen); + + err = sendCommandWithResponse(command, "@", response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT * 5); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + unsigned long writeDelay = millis(); + while (millis() < (writeDelay + 50)) + delay(1); // u-blox specification says to wait 50ms after receiving "@" to write data. + + if (len == -1) + { + if (_printDebug == true) + { + _debugPort->print(F("socketWrite: writing: ")); + _debugPort->println(str); + } + hwPrint(str); + } + else + { + if (_printDebug == true) + { + _debugPort->print(F("socketWrite: writing ")); + _debugPort->print(len); + _debugPort->println(F(" bytes")); + } + hwWriteData(str, len); + } + + err = waitForResponse(UBX_CELL_RESPONSE_OK, UBX_CELL_RESPONSE_ERROR, UBX_CELL_SOCKET_WRITE_TIMEOUT); + } + + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("socketWrite: Error: ")); + _debugPort->print(err); + _debugPort->print(F(" => {")); + _debugPort->print(response); + _debugPort->println(F("}")); + } + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketWrite(int socket, String str) +{ + return socketWrite(socket, str.c_str(), str.length()); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketWriteUDP(int socket, const char *address, int port, const char *str, int len) +{ + size_t cmdLen = 64; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int dataLen = len == -1 ? strlen(str) : len; + + snprintf(command, cmdLen, "%s=%d,\"%s\",%d,%d", UBX_CELL_WRITE_UDP_SOCKET, socket, address, port, dataLen); + err = sendCommandWithResponse(command, "@", response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT * 5); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + if (len == -1) // If binary data we need to send a length. + { + hwPrint(str); + } + else + { + hwWriteData(str, len); + } + err = waitForResponse(UBX_CELL_RESPONSE_OK, UBX_CELL_RESPONSE_ERROR, UBX_CELL_SOCKET_WRITE_TIMEOUT); + } + else + { + if (_printDebug == true) + _debugPort->print(F("socketWriteUDP: Error: ")); + if (_printDebug == true) + _debugPort->println(socketGetLastError()); + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketWriteUDP(int socket, IPAddress address, int port, const char *str, int len) +{ + size_t charLen = 16; + char charAddress[16]; + memset(charAddress, 0, 16); + snprintf(charAddress, charLen, "%d.%d.%d.%d", address[0], address[1], address[2], address[3]); + + return (socketWriteUDP(socket, (const char *)charAddress, port, str, len)); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketWriteUDP(int socket, String address, int port, String str) +{ + return socketWriteUDP(socket, address.c_str(), port, str.c_str(), str.length()); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketRead(int socket, int length, char *readDest, int *bytesRead) +{ + size_t cmdLen = strlen(UBX_CELL_READ_SOCKET) + 32; + char command[cmdLen]; + // We only need enough to read _saraR5maxSocketRead bytes - not the whole thing + int responseLength = _saraR5maxSocketRead + strlen(UBX_CELL_READ_SOCKET) + minimumResponseAllocation; + char response[responseLength]; + char *strBegin; + int readIndexTotal = 0; + int readIndexThisRead = 0; + UBX_CELL_error_t err; + int scanNum = 0; + int readLength = 0; + int socketStore = 0; + int bytesLeftToRead = length; + int bytesToRead; + + // Set *bytesRead to zero + if (bytesRead != nullptr) + *bytesRead = 0; + + // Check if length is zero + if (length == 0) + { + if (_printDebug == true) + _debugPort->print(F("socketRead: length is 0! Call socketReadAvailable?")); + return UBX_CELL_ERROR_UNEXPECTED_PARAM; + } + + // If there are more than _saraR5maxSocketRead (1024) bytes to be read, + // we need to do multiple reads to get all the data + + while (bytesLeftToRead > 0) + { + if (bytesLeftToRead > _saraR5maxSocketRead) // Limit a single read to _saraR5maxSocketRead + bytesToRead = _saraR5maxSocketRead; + else + bytesToRead = bytesLeftToRead; + + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_READ_SOCKET, socket, bytesToRead); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT, responseLength); + + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("socketRead: sendCommandWithResponse err ")); + _debugPort->println(err); + } + return err; + } + + // Extract the data + char *searchPtr = strnstr(response, "+USORD:", responseLength); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USORD:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,%d", &socketStore, &readLength); + } + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("socketRead: error: scanNum is ")); + _debugPort->println(scanNum); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + // Check that readLength == bytesToRead + if (readLength != bytesToRead) + { + if (_printDebug == true) + { + _debugPort->print(F("socketRead: length mismatch! bytesToRead=")); + _debugPort->print(bytesToRead); + _debugPort->print(F(" readLength=")); + _debugPort->println(readLength); + } + } + + // Check that readLength > 0 + if (readLength == 0) + { + if (_printDebug == true) + { + _debugPort->println(F("socketRead: zero length!")); + } + return UBX_CELL_ERROR_ZERO_READ_LENGTH; + } + + // Find the first double-quote: + strBegin = strchr(searchPtr, '\"'); + + if (strBegin == nullptr) + { + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + // Now copy the data into readDest + readIndexThisRead = 1; // Start after the quote + while (readIndexThisRead < (readLength + 1)) + { + readDest[readIndexTotal] = strBegin[readIndexThisRead]; + readIndexTotal++; + readIndexThisRead++; + } + + if (_printDebug == true) + _debugPort->println(F("socketRead: success")); + + // Update *bytesRead + if (bytesRead != nullptr) + *bytesRead = readIndexTotal; + + // How many bytes are left to read? + // This would have been bytesLeftToRead -= bytesToRead + // Except the SARA can potentially return less data than requested... + // So we need to subtract readLength instead. + bytesLeftToRead -= readLength; + if (_printDebug == true) + { + if (bytesLeftToRead > 0) + { + _debugPort->print(F("socketRead: multiple read. bytesLeftToRead: ")); + _debugPort->println(bytesLeftToRead); + } + } + } // /while (bytesLeftToRead > 0) + + return UBX_CELL_ERROR_SUCCESS; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketReadAvailable(int socket, int *length) +{ + size_t cmdLen = strlen(UBX_CELL_READ_SOCKET) + 32; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int scanNum = 0; + int readLength = 0; + int socketStore = 0; + + snprintf(command, cmdLen, "%s=%d,0", UBX_CELL_READ_SOCKET, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+USORD:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USORD:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,%d", &socketStore, &readLength); + } + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("socketReadAvailable: error: scanNum is ")); + _debugPort->println(scanNum); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + *length = readLength; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketReadUDP(int socket, int length, char *readDest, IPAddress *remoteIPAddress, + int *remotePort, int *bytesRead) +{ + size_t cmdLen = strlen(UBX_CELL_READ_UDP_SOCKET) + 32; + char command[cmdLen]; + // We only need enough to read _saraR5maxSocketRead bytes - not the whole thing + int responseLength = _saraR5maxSocketRead + strlen(UBX_CELL_READ_UDP_SOCKET) + minimumResponseAllocation; + char response[responseLength]; + char *strBegin; + int readIndexTotal = 0; + int readIndexThisRead = 0; + UBX_CELL_error_t err; + int scanNum = 0; + int remoteIPstore[4] = {0, 0, 0, 0}; + int portStore = 0; + int readLength = 0; + int socketStore = 0; + int bytesLeftToRead = length; + int bytesToRead; + + // Set *bytesRead to zero + if (bytesRead != nullptr) + *bytesRead = 0; + + // Check if length is zero + if (length == 0) + { + if (_printDebug == true) + _debugPort->print(F("socketReadUDP: length is 0! Call socketReadAvailableUDP?")); + return UBX_CELL_ERROR_UNEXPECTED_PARAM; + } + + // If there are more than _saraR5maxSocketRead (1024) bytes to be read, + // we need to do multiple reads to get all the data + + while (bytesLeftToRead > 0) + { + if (bytesLeftToRead > _saraR5maxSocketRead) // Limit a single read to _saraR5maxSocketRead + bytesToRead = _saraR5maxSocketRead; + else + bytesToRead = bytesLeftToRead; + + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_READ_UDP_SOCKET, socket, bytesToRead); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT, responseLength); + + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("socketReadUDP: sendCommandWithResponse err ")); + _debugPort->println(err); + } + return err; + } + + // Extract the data + char *searchPtr = strnstr(response, "+USORF:", responseLength); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USORF:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%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); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + // Check that readLength == bytesToRead + if (readLength != bytesToRead) + { + if (_printDebug == true) + { + _debugPort->print(F("socketReadUDP: length mismatch! bytesToRead=")); + _debugPort->print(bytesToRead); + _debugPort->print(F(" readLength=")); + _debugPort->println(readLength); + } + } + + // Check that readLength > 0 + if (readLength == 0) + { + if (_printDebug == true) + { + _debugPort->println(F("socketRead: zero length!")); + } + return UBX_CELL_ERROR_ZERO_READ_LENGTH; + } + + // Find the third double-quote + strBegin = strchr(searchPtr, '\"'); + strBegin = strchr(strBegin + 1, '\"'); + strBegin = strchr(strBegin + 1, '\"'); + + if (strBegin == nullptr) + { + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + // Now copy the data into readDest + readIndexThisRead = 1; // Start after the quote + while (readIndexThisRead < (readLength + 1)) + { + readDest[readIndexTotal] = strBegin[readIndexThisRead]; + readIndexTotal++; + readIndexThisRead++; + } + + // If remoteIPaddress is not nullptr, copy the remote IP address + if (remoteIPAddress != nullptr) + { + IPAddress tempAddress; + for (int i = 0; i <= 3; i++) + { + tempAddress[i] = (uint8_t)remoteIPstore[i]; + } + *remoteIPAddress = tempAddress; + } + + // If remotePort is not nullptr, copy the remote port + if (remotePort != nullptr) + { + *remotePort = portStore; + } + + if (_printDebug == true) + _debugPort->println(F("socketReadUDP: success")); + + // Update *bytesRead + if (bytesRead != nullptr) + *bytesRead = readIndexTotal; + + // How many bytes are left to read? + // This would have been bytesLeftToRead -= bytesToRead + // Except the SARA can potentially return less data than requested... + // So we need to subtract readLength instead. + bytesLeftToRead -= readLength; + if (_printDebug == true) + { + if (bytesLeftToRead > 0) + { + _debugPort->print(F("socketReadUDP: multiple read. bytesLeftToRead: ")); + _debugPort->println(bytesLeftToRead); + } + } + } // /while (bytesLeftToRead > 0) + + return UBX_CELL_ERROR_SUCCESS; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketReadAvailableUDP(int socket, int *length) +{ + size_t cmdLen = strlen(UBX_CELL_READ_UDP_SOCKET) + 32; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int scanNum = 0; + int readLength = 0; + int socketStore = 0; + + snprintf(command, cmdLen, "%s=%d,0", UBX_CELL_READ_UDP_SOCKET, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+USORF:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USORF:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,%d", &socketStore, &readLength); + } + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("socketReadAvailableUDP: error: scanNum is ")); + _debugPort->println(scanNum); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + *length = readLength; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketListen(int socket, unsigned int port) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_LISTEN_SOCKET) + 9; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_LISTEN_SOCKET, socket, port); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketDirectLinkMode(int socket) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_SOCKET_DIRECT_LINK) + 8; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_SOCKET_DIRECT_LINK, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_CONNECT, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketDirectLinkTimeTrigger(int socket, unsigned long timerTrigger) +{ + // valid range is 0 (trigger disabled), 100-120000 + if (!((timerTrigger == 0) || ((timerTrigger >= 100) && (timerTrigger <= 120000)))) + return UBX_CELL_ERROR_ERROR; + + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_UD_CONFIGURATION) + 16; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=5,%d,%ld", UBX_CELL_UD_CONFIGURATION, socket, timerTrigger); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketDirectLinkDataLengthTrigger(int socket, int dataLengthTrigger) +{ + // valid range is 0, 3-1472 for UDP + if (!((dataLengthTrigger == 0) || ((dataLengthTrigger >= 3) && (dataLengthTrigger <= 1472)))) + return UBX_CELL_ERROR_ERROR; + + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_UD_CONFIGURATION) + 16; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=6,%d,%d", UBX_CELL_UD_CONFIGURATION, socket, dataLengthTrigger); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketDirectLinkCharacterTrigger(int socket, int characterTrigger) +{ + // The allowed range is -1, 0-255, the factory-programmed value is -1; -1 means trigger disabled. + if (!((characterTrigger >= -1) && (characterTrigger <= 255))) + return UBX_CELL_ERROR_ERROR; + + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_UD_CONFIGURATION) + 16; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=7,%d,%d", UBX_CELL_UD_CONFIGURATION, socket, characterTrigger); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::socketDirectLinkCongestionTimer(int socket, unsigned long congestionTimer) +{ + // valid range is 0, 1000-72000 + if (!((congestionTimer == 0) || ((congestionTimer >= 1000) && (congestionTimer <= 72000)))) + return UBX_CELL_ERROR_ERROR; + + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_UD_CONFIGURATION) + 16; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=8,%d,%ld", UBX_CELL_UD_CONFIGURATION, socket, congestionTimer); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::querySocketType(int socket, UBX_CELL_socket_protocol_t *protocol) +{ + size_t cmdLen = strlen(UBX_CELL_SOCKET_CONTROL) + 16; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int scanNum = 0; + int socketStore = 0; + int paramVal; + + snprintf(command, cmdLen, "%s=%d,0", UBX_CELL_SOCKET_CONTROL, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+USOCTL:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USOCTL:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,0,%d", &socketStore, ¶mVal); + } + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketType: error: scanNum is ")); + _debugPort->println(scanNum); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + *protocol = (UBX_CELL_socket_protocol_t)paramVal; + _lastSocketProtocol[socketStore] = paramVal; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::querySocketLastError(int socket, int *error) +{ + size_t cmdLen = strlen(UBX_CELL_SOCKET_CONTROL) + 16; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int scanNum = 0; + int socketStore = 0; + int paramVal; + + snprintf(command, cmdLen, "%s=%d,1", UBX_CELL_SOCKET_CONTROL, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+USOCTL:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USOCTL:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,1,%d", &socketStore, ¶mVal); + } + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketLastError: error: scanNum is ")); + _debugPort->println(scanNum); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + *error = paramVal; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::querySocketTotalBytesSent(int socket, uint32_t *total) +{ + size_t cmdLen = strlen(UBX_CELL_SOCKET_CONTROL) + 16; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int scanNum = 0; + int socketStore = 0; + long unsigned int paramVal; + + snprintf(command, cmdLen, "%s=%d,2", UBX_CELL_SOCKET_CONTROL, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+USOCTL:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USOCTL:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,2,%lu", &socketStore, ¶mVal); + } + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketTotalBytesSent: error: scanNum is ")); + _debugPort->println(scanNum); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + *total = (uint32_t)paramVal; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::querySocketTotalBytesReceived(int socket, uint32_t *total) +{ + size_t cmdLen = strlen(UBX_CELL_SOCKET_CONTROL) + 16; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int scanNum = 0; + int socketStore = 0; + long unsigned int paramVal; + + snprintf(command, cmdLen, "%s=%d,3", UBX_CELL_SOCKET_CONTROL, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+USOCTL:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USOCTL:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,3,%lu", &socketStore, ¶mVal); + } + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketTotalBytesReceived: error: scanNum is ")); + _debugPort->println(scanNum); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + *total = (uint32_t)paramVal; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::querySocketRemoteIPAddress(int socket, IPAddress *address, int *port) +{ + size_t cmdLen = strlen(UBX_CELL_SOCKET_CONTROL) + 16; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int scanNum = 0; + int socketStore = 0; + int paramVals[5]; + + snprintf(command, cmdLen, "%s=%d,4", UBX_CELL_SOCKET_CONTROL, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+USOCTL:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USOCTL:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%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); + } + return UBX_CELL_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]; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::querySocketStatusTCP(int socket, UBX_CELL_tcp_socket_status_t *status) +{ + size_t cmdLen = strlen(UBX_CELL_SOCKET_CONTROL) + 16; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int scanNum = 0; + int socketStore = 0; + int paramVal; + + snprintf(command, cmdLen, "%s=%d,10", UBX_CELL_SOCKET_CONTROL, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+USOCTL:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USOCTL:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,10,%d", &socketStore, ¶mVal); + } + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketStatusTCP: error: scanNum is ")); + _debugPort->println(scanNum); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + *status = (UBX_CELL_tcp_socket_status_t)paramVal; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::querySocketOutUnackData(int socket, uint32_t *total) +{ + size_t cmdLen = strlen(UBX_CELL_SOCKET_CONTROL) + 16; + char command[cmdLen]; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + int scanNum = 0; + int socketStore = 0; + long unsigned int paramVal; + + snprintf(command, cmdLen, "%s=%d,11", UBX_CELL_SOCKET_CONTROL, socket); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+USOCTL:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USOCTL:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = sscanf(searchPtr, "%d,11,%lu", &socketStore, ¶mVal); + } + if (scanNum != 2) + { + if (_printDebug == true) + { + _debugPort->print(F("querySocketOutUnackData: error: scanNum is ")); + _debugPort->println(scanNum); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + *total = (uint32_t)paramVal; + } + + return err; +} + +// Issues command to get last socket error, then prints to serial. Also updates rx/backlog buffers. +int SparkFun_ublox_Cellular::socketGetLastError() +{ + UBX_CELL_error_t err; + size_t cmdLen = 64; + char command[cmdLen]; + char response[minimumResponseAllocation]; + int errorCode = -1; + + snprintf(command, cmdLen, "%s", UBX_CELL_GET_ERROR); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + char *searchPtr = strnstr(response, "+USOER:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+USOER:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + sscanf(searchPtr, "%d", &errorCode); + } + } + + return errorCode; +} + +IPAddress SparkFun_ublox_Cellular::lastRemoteIP(void) +{ + return _lastRemoteIP; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::resetHTTPprofile(int profile) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_PROFILE) + 16; + char command[cmdLen]; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_HTTP_PROFILE, profile); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setHTTPserverIPaddress(int profile, IPAddress address) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_PROFILE) + 64; + char command[cmdLen]; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + snprintf(command, cmdLen, "%s=%d,%d,\"%d.%d.%d.%d\"", UBX_CELL_HTTP_PROFILE, profile, + UBX_CELL_HTTP_OP_CODE_SERVER_IP, address[0], address[1], address[2], address[3]); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setHTTPserverName(int profile, String server) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_PROFILE) + 12 + server.length(); + char *command; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,%d,\"%s\"", UBX_CELL_HTTP_PROFILE, profile, UBX_CELL_HTTP_OP_CODE_SERVER_NAME, + server.c_str()); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setHTTPusername(int profile, String username) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_PROFILE) + 12 + username.length(); + char *command; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,%d,\"%s\"", UBX_CELL_HTTP_PROFILE, profile, UBX_CELL_HTTP_OP_CODE_USERNAME, + username.c_str()); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setHTTPpassword(int profile, String password) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_PROFILE) + 12 + password.length(); + char *command; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,%d,\"%s\"", UBX_CELL_HTTP_PROFILE, profile, UBX_CELL_HTTP_OP_CODE_PASSWORD, + password.c_str()); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setHTTPauthentication(int profile, bool authenticate) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_PROFILE) + 32; + char command[cmdLen]; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + snprintf(command, cmdLen, "%s=%d,%d,%d", UBX_CELL_HTTP_PROFILE, profile, UBX_CELL_HTTP_OP_CODE_AUTHENTICATION, + authenticate); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setHTTPserverPort(int profile, int port) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_PROFILE) + 32; + char command[cmdLen]; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + snprintf(command, cmdLen, "%s=%d,%d,%d", UBX_CELL_HTTP_PROFILE, profile, UBX_CELL_HTTP_OP_CODE_SERVER_PORT, port); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setHTTPcustomHeader(int profile, String header) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_PROFILE) + 12 + header.length(); + char *command; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,%d,\"%s\"", UBX_CELL_HTTP_PROFILE, profile, + UBX_CELL_HTTP_OP_CODE_ADD_CUSTOM_HEADERS, header.c_str()); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setHTTPsecure(int profile, bool secure, int secprofile) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_PROFILE) + 32; + char command[cmdLen]; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + if (secprofile == -1) + snprintf(command, cmdLen, "%s=%d,%d,%d", UBX_CELL_HTTP_PROFILE, profile, UBX_CELL_HTTP_OP_CODE_SECURE, secure); + else + snprintf(command, cmdLen, "%s=%d,%d,%d,%d", UBX_CELL_HTTP_PROFILE, profile, UBX_CELL_HTTP_OP_CODE_SECURE, + secure, secprofile); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::ping(String remote_host, int retry, int p_size, unsigned long timeout, int ttl) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_PING_COMMAND) + 48 + remote_host.length(); + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=\"%s\",%d,%d,%ld,%d", UBX_CELL_PING_COMMAND, remote_host.c_str(), retry, p_size, + timeout, ttl); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::sendHTTPGET(int profile, String path, String responseFilename) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_COMMAND) + 24 + path.length() + responseFilename.length(); + char *command; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,%d,\"%s\",\"%s\"", UBX_CELL_HTTP_COMMAND, profile, UBX_CELL_HTTP_COMMAND_GET, + path.c_str(), responseFilename.c_str()); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::sendHTTPPOSTdata(int profile, String path, String responseFilename, String data, + UBX_CELL_http_content_types_t httpContentType) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_COMMAND) + 24 + path.length() + responseFilename.length() + data.length(); + char *command; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,%d,\"%s\",\"%s\",\"%s\",%d", UBX_CELL_HTTP_COMMAND, profile, + UBX_CELL_HTTP_COMMAND_POST_DATA, path.c_str(), responseFilename.c_str(), data.c_str(), httpContentType); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::sendHTTPPOSTfile(int profile, String path, String responseFilename, String requestFile, + UBX_CELL_http_content_types_t httpContentType) +{ + UBX_CELL_error_t err; + size_t cmdLen = + strlen(UBX_CELL_HTTP_COMMAND) + 24 + path.length() + responseFilename.length() + requestFile.length(); + char *command; + + if (profile >= UBX_CELL_NUM_HTTP_PROFILES) + return UBX_CELL_ERROR_ERROR; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,%d,\"%s\",\"%s\",\"%s\",%d", UBX_CELL_HTTP_COMMAND, profile, + UBX_CELL_HTTP_COMMAND_POST_FILE, path.c_str(), responseFilename.c_str(), requestFile.c_str(), + httpContentType); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getHTTPprotocolError(int profile, int *error_class, int *error_code) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_HTTP_PROTOCOL_ERROR) + 4; + char command[cmdLen]; + char response[minimumResponseAllocation]; + + int rprofile, eclass, ecode; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_HTTP_PROTOCOL_ERROR, profile); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + int scanned = 0; + char *searchPtr = strnstr(response, "+UHTTPER:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+UHTTPER:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%d,%d,%d\r\n", &rprofile, &eclass, &ecode); + } + if (scanned == 3) + { + *error_class = eclass; + *error_code = ecode; + } + else + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::nvMQTT(UBX_CELL_mqtt_nv_parameter_t parameter) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_NVM) + 10; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_MQTT_NVM, parameter); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setMQTTclientId(const String &clientId) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_PROFILE) + clientId.length() + 10; + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,\"%s\"", UBX_CELL_MQTT_PROFILE, UBX_CELL_MQTT_PROFILE_CLIENT_ID, clientId.c_str()); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setMQTTserver(const String &serverName, int port) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_PROFILE) + serverName.length() + 16; + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,\"%s\",%d", UBX_CELL_MQTT_PROFILE, UBX_CELL_MQTT_PROFILE_SERVERNAME, + serverName.c_str(), port); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setMQTTcredentials(const String &userName, const String &pwd) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_PROFILE) + userName.length() + pwd.length() + 16; + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,\"%s\",\"%s\"", UBX_CELL_MQTT_PROFILE, UBX_CELL_MQTT_PROFILE_USERNAMEPWD, + userName.c_str(), pwd.c_str()); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setMQTTsecure(bool secure, int secprofile) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_PROFILE) + 16; + char command[cmdLen]; + + if (secprofile == -1) + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_MQTT_PROFILE, UBX_CELL_MQTT_PROFILE_SECURE, secure); + else + snprintf(command, cmdLen, "%s=%d,%d,%d", UBX_CELL_MQTT_PROFILE, UBX_CELL_MQTT_PROFILE_SECURE, secure, + secprofile); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::connectMQTT(void) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_COMMAND) + 10; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_MQTT_COMMAND, UBX_CELL_MQTT_COMMAND_LOGIN); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::disconnectMQTT(void) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_COMMAND) + 10; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_MQTT_COMMAND, UBX_CELL_MQTT_COMMAND_LOGOUT); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::subscribeMQTTtopic(int max_Qos, const String &topic) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_COMMAND) + 16 + topic.length(); + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,%d,\"%s\"", UBX_CELL_MQTT_COMMAND, UBX_CELL_MQTT_COMMAND_SUBSCRIBE, max_Qos, + topic.c_str()); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::unsubscribeMQTTtopic(const String &topic) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_COMMAND) + 16 + topic.length(); + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,\"%s\"", UBX_CELL_MQTT_COMMAND, UBX_CELL_MQTT_COMMAND_UNSUBSCRIBE, topic.c_str()); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::readMQTT(int *pQos, String *pTopic, uint8_t *readDest, int readLength, int *bytesRead) +{ + size_t cmdLen = strlen(UBX_CELL_MQTT_COMMAND) + 10; + char command[cmdLen]; + char *response; + UBX_CELL_error_t err; + int scanNum = 0; + int total_length, topic_length, data_length; + + // Set *bytesRead to zero + if (bytesRead != nullptr) + *bytesRead = 0; + + // Allocate memory for the response + int responseLength = readLength + minimumResponseAllocation; + response = ubx_cell_calloc_char(responseLength); + if (response == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + + // Note to self: if the file contents contain "OK\r\n" sendCommandWithResponse will return true too early... + // To try and avoid this, look for \"\r\n\r\nOK\r\n there is a extra \r\n beetween " and the the standard \r\nOK\r\n + const char mqttReadTerm[] = "\"\r\n\r\nOK\r\n"; + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_MQTT_COMMAND, UBX_CELL_MQTT_COMMAND_READ, 1); + err = sendCommandWithResponse(command, mqttReadTerm, response, (5 * UBX_CELL_STANDARD_RESPONSE_TIMEOUT), + responseLength); + + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("readMQTT: sendCommandWithResponse err ")); + _debugPort->println(err); + } + free(response); + return err; + } + + // Extract the data + char *searchPtr = strnstr(response, "+UMQTTC:", responseLength); + int cmd = 0; + if (searchPtr != nullptr) + { + searchPtr += strlen("+UMQTTC:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanNum = + sscanf(searchPtr, "%d,%d,%d,%d,\"%*[^\"]\",%d,\"", &cmd, pQos, &total_length, &topic_length, &data_length); + } + if ((scanNum != 5) || (cmd != UBX_CELL_MQTT_COMMAND_READ)) + { + if (_printDebug == true) + { + _debugPort->print(F("readMQTT: error: scanNum is ")); + _debugPort->println(scanNum); + } + free(response); + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + err = UBX_CELL_ERROR_SUCCESS; + searchPtr = strnstr(searchPtr, "\"", responseLength - (searchPtr - response)); + if (searchPtr != nullptr) + { + if (pTopic) + { + searchPtr[topic_length + 1] = '\0'; // zero terminate + *pTopic = searchPtr + 1; + searchPtr[topic_length + 1] = '\"'; // restore + } + searchPtr = + strnstr(searchPtr + topic_length + 2, "\"", responseLength - (searchPtr + topic_length + 2 - response)); + if (readDest && (searchPtr != nullptr) && (response + responseLength >= searchPtr + data_length + 1) && + (searchPtr[data_length + 1] == '"')) + { + if (data_length > readLength) + { + data_length = readLength; + if (_printDebug == true) + { + _debugPort->print(F("readMQTT: error: trucate message")); + } + err = UBX_CELL_ERROR_OUT_OF_MEMORY; + } + memcpy(readDest, searchPtr + 1, data_length); + *bytesRead = data_length; + } + else + { + if (_printDebug == true) + { + _debugPort->print(F("readMQTT: error: message end ")); + } + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + } + free(response); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::mqttPublishTextMsg(const String &topic, const char *const msg, uint8_t qos, bool retain) +{ + if (topic.length() < 1 || msg == nullptr) + { + return UBX_CELL_ERROR_INVALID; + } + + UBX_CELL_error_t err; + + char sanitized_msg[MAX_MQTT_DIRECT_MSG_LEN + 1]; + memset(sanitized_msg, 0, sizeof(sanitized_msg)); + + // Check the message length and truncate if necessary. + size_t msg_len = strnlen(msg, MAX_MQTT_DIRECT_MSG_LEN); + if (msg_len > MAX_MQTT_DIRECT_MSG_LEN) + { + msg_len = MAX_MQTT_DIRECT_MSG_LEN; + } + + strncpy(sanitized_msg, msg, msg_len); + char *msg_ptr = sanitized_msg; + while (*msg_ptr != 0) + { + if (*msg_ptr == '"') + { + *msg_ptr = ' '; + } + + msg_ptr++; + } + + size_t cmdLen = strlen(UBX_CELL_MQTT_COMMAND) + 20 + topic.length() + msg_len; + char *command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + + snprintf(command, cmdLen, "%s=%d,%u,%u,0,\"%s\",\"%s\"", UBX_CELL_MQTT_COMMAND, UBX_CELL_MQTT_COMMAND_PUBLISH, qos, + (retain ? 1 : 0), topic.c_str(), sanitized_msg); + + sendCommand(command, true); + err = waitForResponse(UBX_CELL_RESPONSE_MORE, UBX_CELL_RESPONSE_ERROR, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + sendCommand(msg, false); + err = waitForResponse(UBX_CELL_RESPONSE_OK, UBX_CELL_RESPONSE_ERROR, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + } + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::mqttPublishBinaryMsg(const String &topic, const char *const msg, size_t msg_len, uint8_t qos, + bool retain) +{ + /* + * The modem prints the '>' as the signal to send the binary message content. + * at+umqttc=9,0,0,"topic",4 + * + * >"xY" + * OK + * + * +UUMQTTC: 9,1 + */ + if (topic.length() < 1 || msg == nullptr || msg_len > MAX_MQTT_DIRECT_MSG_LEN) + { + return UBX_CELL_ERROR_INVALID; + } + + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_COMMAND) + 20 + topic.length(); + char *command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + + snprintf(command, cmdLen, "%s=%d,%u,%u,\"%s\",%u", UBX_CELL_MQTT_COMMAND, UBX_CELL_MQTT_COMMAND_PUBLISHBINARY, qos, + (retain ? 1 : 0), topic.c_str(), msg_len); + + sendCommand(command, true); + err = waitForResponse(UBX_CELL_RESPONSE_MORE, UBX_CELL_RESPONSE_ERROR, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + sendCommand(msg, false); + err = waitForResponse(UBX_CELL_RESPONSE_OK, UBX_CELL_RESPONSE_ERROR, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + } + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::mqttPublishFromFile(const String &topic, const String &filename, uint8_t qos, bool retain) +{ + if (topic.length() < 1 || filename.length() < 1) + { + return UBX_CELL_ERROR_INVALID; + } + + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MQTT_COMMAND) + 20 + topic.length() + filename.length(); + char *command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + + snprintf(command, cmdLen, "%s=%d,%u,%u,\"%s\",\"%s\"", UBX_CELL_MQTT_COMMAND, UBX_CELL_MQTT_COMMAND_PUBLISHFILE, + qos, (retain ? 1 : 0), topic.c_str(), filename.c_str()); + + sendCommand(command, true); + err = waitForResponse(UBX_CELL_RESPONSE_OK, UBX_CELL_RESPONSE_ERROR, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getMQTTprotocolError(int *error_code, int *error_code2) +{ + UBX_CELL_error_t err; + char response[minimumResponseAllocation]; + + int code, code2; + + err = sendCommandWithResponse(UBX_CELL_MQTT_PROTOCOL_ERROR, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + int scanned = 0; + char *searchPtr = strnstr(response, "+UMQTTER:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+UMQTTER:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%d,%d\r\n", &code, &code2); + } + if (scanned == 2) + { + *error_code = code; + *error_code2 = code2; + } + else + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setFTPserver(const String &serverName) +{ + constexpr size_t cmdLen = 145; + char command[cmdLen]; // long enough for AT+UFTP=1,<128 bytes> + + snprintf(command, cmdLen, "%s=%d,\"%s\"", UBX_CELL_FTP_PROFILE, UBX_CELL_FTP_PROFILE_SERVERNAME, + serverName.c_str()); + return sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setFTPtimeouts(const unsigned int timeout, const unsigned int cmd_linger, + const unsigned int data_linger) +{ + constexpr size_t cmdLen = 64; + char command[cmdLen]; // long enough for AT+UFTP=1,<128 bytes> + + snprintf(command, cmdLen, "%s=%d,%u,%u,%u", UBX_CELL_FTP_PROFILE, UBX_CELL_FTP_PROFILE_TIMEOUT, timeout, cmd_linger, + data_linger); + return sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setFTPcredentials(const String &userName, const String &pwd) +{ + UBX_CELL_error_t err; + constexpr size_t cmdLen = 48; + char command[cmdLen]; // long enough for AT+UFTP=n,<30 bytes> + + snprintf(command, cmdLen, "%s=%d,\"%s\"", UBX_CELL_FTP_PROFILE, UBX_CELL_FTP_PROFILE_USERNAME, userName.c_str()); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err != UBX_CELL_ERROR_SUCCESS) + { + return err; + } + + snprintf(command, cmdLen, "%s=%d,\"%s\"", UBX_CELL_FTP_PROFILE, UBX_CELL_FTP_PROFILE_PWD, pwd.c_str()); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::connectFTP(void) +{ + constexpr size_t cmdLen = 16; + char command[cmdLen]; // long enough for AT+UFTPC=n + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_FTP_COMMAND, UBX_CELL_FTP_COMMAND_LOGIN); + return sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::disconnectFTP(void) +{ + constexpr size_t cmdLen = 16; + char command[cmdLen]; // long enough for AT+UFTPC=n + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_FTP_COMMAND, UBX_CELL_FTP_COMMAND_LOGOUT); + return sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::ftpGetFile(const String &filename) +{ + size_t cmdLen = strlen(UBX_CELL_FTP_COMMAND) + (2 * filename.length()) + 16; + char *command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + + snprintf(command, cmdLen, "%s=%d,\"%s\",\"%s\"", UBX_CELL_FTP_COMMAND, UBX_CELL_FTP_COMMAND_GET_FILE, + filename.c_str(), filename.c_str()); + // memset(response, 0, sizeof(response)); + // sendCommandWithResponse(command, UBX_CELL_RESPONSE_CONNECT, response, 8000 /* ms */, response_len); + UBX_CELL_error_t err = + sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getFTPprotocolError(int *error_code, int *error_code2) +{ + UBX_CELL_error_t err; + char response[minimumResponseAllocation]; + + int code, code2; + + err = sendCommandWithResponse(UBX_CELL_FTP_PROTOCOL_ERROR, UBX_CELL_RESPONSE_OK_OR_ERROR, response, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + int scanned = 0; + char *searchPtr = strnstr(response, "+UFTPER:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+UFTPER:"); // Move searchPtr to first char + while (*searchPtr == ' ') + { + searchPtr++; // skip spaces + } + scanned = sscanf(searchPtr, "%d,%d\r\n", &code, &code2); + } + + if (scanned == 2) + { + *error_code = code; + *error_code2 = code2; + } + else + { + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::resetSecurityProfile(int secprofile) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_SEC_PROFILE) + 6; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_SEC_PROFILE, secprofile); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::configSecurityProfile(int secprofile, UBX_CELL_sec_profile_parameter_t parameter, int value) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_SEC_PROFILE) + 10; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d,%d,%d", UBX_CELL_SEC_PROFILE, secprofile, parameter, value); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::configSecurityProfileString(int secprofile, UBX_CELL_sec_profile_parameter_t parameter, + String value) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_SEC_PROFILE) + value.length() + 10; + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=%d,%d,\"%s\"", UBX_CELL_SEC_PROFILE, secprofile, parameter, value.c_str()); + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setSecurityManager(UBX_CELL_sec_manager_opcode_t opcode, + UBX_CELL_sec_manager_parameter_t parameter, String name, String data) +{ + size_t cmdLen = strlen(UBX_CELL_SEC_MANAGER) + name.length() + 20; + char *command; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + int dataLen = data.length(); + snprintf(command, cmdLen, "%s=%d,%d,\"%s\",%d", UBX_CELL_SEC_MANAGER, opcode, parameter, name.c_str(), dataLen); + + err = sendCommandWithResponse(command, ">", response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("dataDownload: writing ")); + _debugPort->print(dataLen); + _debugPort->println(F(" bytes")); + } + hwWriteData(data.c_str(), dataLen); + err = waitForResponse(UBX_CELL_RESPONSE_OK, UBX_CELL_RESPONSE_ERROR, UBX_CELL_STANDARD_RESPONSE_TIMEOUT * 3); + } + + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("dataDownload: Error: ")); + _debugPort->print(err); + _debugPort->print(F(" => {")); + _debugPort->print(response); + _debugPort->println(F("}")); + } + } + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::activatePDPcontext(bool status, int cid) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_MESSAGE_PDP_CONTEXT_ACTIVATE) + 32; + char command[cmdLen]; + + if (cid >= UBX_CELL_NUM_PDP_CONTEXT_IDENTIFIERS) + return UBX_CELL_ERROR_ERROR; + + if (cid == -1) + snprintf(command, cmdLen, "%s=%d", UBX_CELL_MESSAGE_PDP_CONTEXT_ACTIVATE, status); + else + snprintf(command, cmdLen, "%s=%d,%d", UBX_CELL_MESSAGE_PDP_CONTEXT_ACTIVATE, status, cid); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +bool SparkFun_ublox_Cellular::isGPSon(void) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_POWER) + 2; + char command[cmdLen]; + char response[minimumResponseAllocation]; + bool on = false; + + snprintf(command, cmdLen, "%s?", UBX_CELL_GNSS_POWER); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_10_SEC_TIMEOUT); + + if (err == UBX_CELL_ERROR_SUCCESS) + { + // Example response: "+UGPS: 0" for off "+UGPS: 1,0,1" for on + // Search for a ':' followed by a '1' or ' 1' + char *pch1 = strchr(response, ':'); + if (pch1 != nullptr) + { + char *pch2 = strchr(response, '1'); + if ((pch2 != nullptr) && ((pch2 == pch1 + 1) || (pch2 == pch1 + 2))) + on = true; + } + } + + return on; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsPower(bool enable, gnss_system_t gnss_sys, gnss_aiding_mode_t gnss_aiding) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_POWER) + 32; // gnss_sys could be up to three digits + char command[cmdLen]; + bool gpsState; + + // Don't turn GPS on/off if it's already on/off + gpsState = isGPSon(); + if ((enable && gpsState) || (!enable && !gpsState)) + { + return UBX_CELL_ERROR_SUCCESS; + } + + // GPS power management + if (enable) + { + snprintf(command, cmdLen, "%s=1,%d,%d", UBX_CELL_GNSS_POWER, gnss_aiding, gnss_sys); + } + else + { + snprintf(command, cmdLen, "%s=0", UBX_CELL_GNSS_POWER); + } + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, 10000); + + return err; +} + +/* +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsEnableClock(bool enable) +{ + // AT+UGZDA=<0,1> + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsGetClock(struct ClockData *clock) +{ + // AT+UGZDA? + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsEnableFix(bool enable) +{ + // AT+UGGGA=<0,1> + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsGetFix(struct PositionData *pos) +{ + // AT+UGGGA? + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsEnablePos(bool enable) +{ + // AT+UGGLL=<0,1> + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsGetPos(struct PositionData *pos) +{ + // AT+UGGLL? + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsEnableSat(bool enable) +{ + // AT+UGGSV=<0,1> + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsGetSat(uint8_t *sats) +{ + // AT+UGGSV? + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + return err; +} +*/ + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsEnableRmc(bool enable) +{ + // AT+UGRMC=<0,1> + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_GPRMC) + 3; + char command[cmdLen]; + + // ** Don't call gpsPower here. It causes problems for +UTIME and the PPS signal ** + // ** Call isGPSon and gpsPower externally if required ** + // if (!isGPSon()) + // { + // err = gpsPower(true); + // if (err != UBX_CELL_ERROR_SUCCESS) + // { + // return err; + // } + // } + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_GNSS_GPRMC, enable ? 1 : 0); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_10_SEC_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsGetRmc(struct PositionData *pos, struct SpeedData *spd, struct ClockData *clk, + bool *valid) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_GPRMC) + 2; + char command[cmdLen]; + char response[minimumResponseAllocation]; + char *rmcBegin; + + snprintf(command, cmdLen, "%s?", UBX_CELL_GNSS_GPRMC); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_10_SEC_TIMEOUT); + if (err == UBX_CELL_ERROR_SUCCESS) + { + // Fast-forward response string to $GPRMC starter + rmcBegin = strnstr(response, "$GPRMC", minimumResponseAllocation); + if (rmcBegin == nullptr) + { + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + else + { + *valid = parseGPRMCString(rmcBegin, pos, clk, spd); + } + } + + return err; +} + +/* +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsEnableSpeed(bool enable) +{ + // AT+UGVTG=<0,1> + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsGetSpeed(struct SpeedData *speed) +{ + // AT+UGVTG? + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + return err; +} +*/ + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsRequest(unsigned int timeout, uint32_t accuracy, bool detailed, unsigned int sensor) +{ + // AT+ULOC=2,,,, + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_GNSS_REQUEST_LOCATION) + 24; + char command[cmdLen]; + + // This function will only work if the GPS module is initially turned off. + if (isGPSon()) + { + gpsPower(false); + } + + if (timeout > 999) + timeout = 999; + if (accuracy > 999999) + accuracy = 999999; + +#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) + snprintf(command, cmdLen, "%s=2,%d,%d,%d,%d", UBX_CELL_GNSS_REQUEST_LOCATION, sensor, detailed ? 1 : 0, timeout, + accuracy); +#else + snprintf(command, cmdLen, "%s=2,%d,%d,%d,%ld", UBX_CELL_GNSS_REQUEST_LOCATION, sensor, detailed ? 1 : 0, timeout, + accuracy); +#endif + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_10_SEC_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::gpsAidingServerConf(const char *primaryServer, const char *secondaryServer, + const char *authToken, unsigned int days, unsigned int period, + unsigned int resolution, unsigned int gnssTypes, unsigned int mode, + unsigned int dataType) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_AIDING_SERVER_CONFIGURATION) + 256; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=\"%s\",\"%s\",\"%s\",%d,%d,%d,%d,%d,%d", UBX_CELL_AIDING_SERVER_CONFIGURATION, + primaryServer, secondaryServer, authToken, days, period, resolution, gnssTypes, mode, dataType); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +// OK for text files. But will fail with binary files (containing \0) on some platforms. +UBX_CELL_error_t SparkFun_ublox_Cellular::appendFileContents(String filename, const char *str, int len) +{ + size_t cmdLen = strlen(UBX_CELL_FILE_SYSTEM_DOWNLOAD_FILE) + filename.length() + 10; + char *command; + char response[minimumResponseAllocation]; + UBX_CELL_error_t err; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + int dataLen = len == -1 ? strlen(str) : len; + snprintf(command, cmdLen, "%s=\"%s\",%d", UBX_CELL_FILE_SYSTEM_DOWNLOAD_FILE, filename.c_str(), dataLen); + + err = sendCommandWithResponse(command, ">", response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT * 2); + + unsigned long writeDelay = millis(); + while (millis() < (writeDelay + 50)) + delay(1); // uBlox specification says to wait 50ms after receiving "@" to write data. + + if (err == UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("fileDownload: writing ")); + _debugPort->print(dataLen); + _debugPort->println(F(" bytes")); + } + hwWriteData(str, dataLen); + + err = waitForResponse(UBX_CELL_RESPONSE_OK, UBX_CELL_RESPONSE_ERROR, UBX_CELL_STANDARD_RESPONSE_TIMEOUT * 5); + } + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("fileDownload: Error: ")); + _debugPort->print(err); + _debugPort->print(F(" => {")); + _debugPort->print(response); + _debugPort->println(F("}")); + } + } + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::appendFileContents(String filename, String str) +{ + return appendFileContents(filename, str.c_str(), str.length()); +} + +// OK for text files. But will fail with binary files (containing \0) on some platforms. +UBX_CELL_error_t SparkFun_ublox_Cellular::getFileContents(String filename, String *contents) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_FILE_SYSTEM_READ_FILE) + filename.length() + 8; + char *command; + char *response; + + // Start by getting the file size so we know in advance how much data to expect + int fileSize = 0; + err = getFileSize(filename, &fileSize); + if (err != UBX_CELL_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("getFileContents: getFileSize returned err ")); + _debugPort->println(err); + } + return err; + } + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=\"%s\"", UBX_CELL_FILE_SYSTEM_READ_FILE, filename.c_str()); + + response = ubx_cell_calloc_char(fileSize + minimumResponseAllocation); + if (response == nullptr) + { + if (_printDebug == true) + { + _debugPort->print(F("getFileContents: response alloc failed: ")); + _debugPort->println(fileSize + minimumResponseAllocation); + } + free(command); + return UBX_CELL_ERROR_OUT_OF_MEMORY; + } + + // A large file will completely fill the backlog buffer - but it will be pruned afterwards + // Note to self: if the file contents contain "OK\r\n" sendCommandWithResponse will return true too early... + // To try and avoid this, look for \"\r\nOK\r\n + const char fileReadTerm[] = "\r\nOK\r\n"; // LARA-R6 returns "\"\r\n\r\nOK\r\n" while SARA-R5 return "\"\r\nOK\r\n"; + err = sendCommandWithResponse(command, fileReadTerm, response, (5 * UBX_CELL_STANDARD_RESPONSE_TIMEOUT), + (fileSize + minimumResponseAllocation)); + + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("getFileContents: sendCommandWithResponse returned err ")); + _debugPort->println(err); + } + free(command); + free(response); + return err; + } + + // Response format: \r\n+URDFILE: "filename",36,"these bytes are the data of the file"\r\n\r\nOK\r\n + int scanned = 0; + int readFileSize = 0; + char *searchPtr = strnstr(response, "+URDFILE:", fileSize + minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr = strchr(searchPtr, '\"'); // Find the first quote + searchPtr = strchr(++searchPtr, '\"'); // Find the second quote + + scanned = sscanf(searchPtr, "\",%d,", &readFileSize); // Get the file size (again) + if (scanned == 1) + { + searchPtr = strchr(++searchPtr, '\"'); // Find the third quote + + if (searchPtr == nullptr) + { + if (_printDebug == true) + { + _debugPort->println(F("getFileContents: third quote not found!")); + } + free(command); + free(response); + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + int bytesRead = 0; + + while (bytesRead < readFileSize) + { + searchPtr++; // Increment searchPtr then copy file char into contents + // Important Note: some implementations of concat, like the one on ESP32, are binary-compatible. + // But some, like SAMD, are not. They use strlen or strcpy internally - which don't like \0's. + // The only true binary-compatible solution is to use getFileContents(String filename, char + // *contents)... + contents->concat(*(searchPtr)); // Append file char to contents + bytesRead++; + } + if (_printDebug == true) + { + _debugPort->print(F("getFileContents: total bytes read: ")); + _debugPort->println(bytesRead); + } + err = UBX_CELL_ERROR_SUCCESS; + } + else + { + if (_printDebug == true) + { + _debugPort->print(F("getFileContents: sscanf failed! scanned is ")); + _debugPort->println(scanned); + } + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + } + else + { + if (_printDebug == true) + _debugPort->println(F("getFileContents: strnstr failed!")); + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + free(command); + free(response); + return err; +} + +// OK for binary files. Make sure contents can hold the entire file. Get the size first with getFileSize. +UBX_CELL_error_t SparkFun_ublox_Cellular::getFileContents(String filename, char *contents) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_FILE_SYSTEM_READ_FILE) + filename.length() + 8; + char *command; + char *response; + + // Start by getting the file size so we know in advance how much data to expect + int fileSize = 0; + err = getFileSize(filename, &fileSize); + if (err != UBX_CELL_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("getFileContents: getFileSize returned err ")); + _debugPort->println(err); + } + return err; + } + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=\"%s\"", UBX_CELL_FILE_SYSTEM_READ_FILE, filename.c_str()); + + response = ubx_cell_calloc_char(fileSize + minimumResponseAllocation); + if (response == nullptr) + { + if (_printDebug == true) + { + _debugPort->print(F("getFileContents: response alloc failed: ")); + _debugPort->println(fileSize + minimumResponseAllocation); + } + free(command); + return UBX_CELL_ERROR_OUT_OF_MEMORY; + } + + // A large file will completely fill the backlog buffer - but it will be pruned afterwards + // Note to self: if the file contents contain "OK\r\n" sendCommandWithResponse will return true too early... + // To try and avoid this, look for \"\r\nOK\r\n + const char fileReadTerm[] = "\"\r\nOK\r\n"; + err = sendCommandWithResponse(command, fileReadTerm, response, (5 * UBX_CELL_STANDARD_RESPONSE_TIMEOUT), + (fileSize + minimumResponseAllocation)); + + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("getFileContents: sendCommandWithResponse returned err ")); + _debugPort->println(err); + } + free(command); + free(response); + return err; + } + + // Response format: \r\n+URDFILE: "filename",36,"these bytes are the data of the file"\r\n\r\nOK\r\n + int scanned = 0; + int readFileSize = 0; + char *searchPtr = strnstr(response, "+URDFILE:", fileSize + minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr = strchr(searchPtr, '\"'); // Find the first quote + searchPtr = strchr(++searchPtr, '\"'); // Find the second quote + + scanned = sscanf(searchPtr, "\",%d,", &readFileSize); // Get the file size (again) + if (scanned == 1) + { + searchPtr = strchr(++searchPtr, '\"'); // Find the third quote + + if (searchPtr == nullptr) + { + if (_printDebug == true) + { + _debugPort->println(F("getFileContents: third quote not found!")); + } + free(command); + free(response); + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + int bytesRead = 0; + + while (bytesRead < readFileSize) + { + searchPtr++; // Increment searchPtr then copy file char into contents + contents[bytesRead] = *searchPtr; // Append file char to contents + bytesRead++; + } + if (_printDebug == true) + { + _debugPort->print(F("getFileContents: total bytes read: ")); + _debugPort->println(bytesRead); + } + err = UBX_CELL_ERROR_SUCCESS; + } + else + { + if (_printDebug == true) + { + _debugPort->print(F("getFileContents: sscanf failed! scanned is ")); + _debugPort->println(scanned); + } + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + } + else + { + if (_printDebug == true) + _debugPort->println(F("getFileContents: strnstr failed!")); + err = UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + free(command); + free(response); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getFileBlock(const String &filename, char *buffer, size_t offset, size_t requested_length, + size_t &bytes_read) +{ + bytes_read = 0; + if (filename.length() < 1 || buffer == nullptr || requested_length < 1) + { + return UBX_CELL_ERROR_UNEXPECTED_PARAM; + } + + // trying to get a byte at a time does not seem to be reliable so this method must use + // a real UART. + if (_hardSerial == nullptr) + { + if (_printDebug == true) + { + _debugPort->println(F("getFileBlock: only works with a hardware UART")); + } + return UBX_CELL_ERROR_INVALID; + } + + size_t cmdLen = filename.length() + 32; + char *cmd = ubx_cell_calloc_char(cmdLen); + if (cmd == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(cmd, cmdLen, "at+urdblock=\"%s\",%zu,%zu\r\n", filename.c_str(), offset, requested_length); + sendCommand(cmd, false); + + int ich; + char ch; + int quote_count = 0; + size_t comma_idx = 0; + + while (quote_count < 3) + { + ich = _hardSerial->read(); + if (ich < 0) + { + continue; + } + ch = (char)(ich & 0xFF); + cmd[bytes_read++] = ch; + if (ch == '"') + { + quote_count++; + } + else if (ch == ',' && comma_idx == 0) + { + comma_idx = bytes_read; + } + } + + cmd[bytes_read] = 0; + cmd[bytes_read - 2] = 0; + + // Example response: + // +URDBLOCK: "wombat.bin",64000,"... " + size_t data_length = strtoul(&cmd[comma_idx], nullptr, 10); + free(cmd); + + bytes_read = 0; + size_t bytes_remaining = data_length; + while (bytes_read < data_length) + { + // This method seems more reliable than reading a byte at a time. + size_t rc = _hardSerial->readBytes(&buffer[bytes_read], bytes_remaining); + bytes_read += rc; + bytes_remaining -= rc; + } + + return UBX_CELL_ERROR_SUCCESS; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getFileSize(String filename, int *size) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_FILE_SYSTEM_LIST_FILES) + filename.length() + 8; + char *command; + char response[minimumResponseAllocation]; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=2,\"%s\"", UBX_CELL_FILE_SYSTEM_LIST_FILES, filename.c_str()); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("getFileSize: Fail: Error: ")); + _debugPort->print(err); + _debugPort->print(F(" Response: {")); + _debugPort->print(response); + _debugPort->println(F("}")); + } + free(command); + return err; + } + + char *responseStart = strnstr(response, "+ULSTFILE:", minimumResponseAllocation); + if (responseStart == nullptr) + { + if (_printDebug == true) + { + _debugPort->print(F("getFileSize: Failure: {")); + _debugPort->print(response); + _debugPort->println(F("}")); + } + free(command); + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + int fileSize; + responseStart += strlen("+ULSTFILE:"); // Move searchPtr to first char + while (*responseStart == ' ') + responseStart++; // skip spaces + sscanf(responseStart, "%d", &fileSize); + *size = fileSize; + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::deleteFile(String filename) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_FILE_SYSTEM_DELETE_FILE) + filename.length() + 8; + char *command; + + command = ubx_cell_calloc_char(cmdLen); + if (command == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + snprintf(command, cmdLen, "%s=\"%s\"", UBX_CELL_FILE_SYSTEM_DELETE_FILE, filename.c_str()); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("deleteFile: Fail: Error: ")); + _debugPort->println(err); + } + } + + free(command); + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::modulePowerOff(void) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_POWER_OFF) + 6; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s", UBX_CELL_COMMAND_POWER_OFF); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_POWER_OFF_TIMEOUT); + + return err; +} + +void SparkFun_ublox_Cellular::modulePowerOn(void) +{ + if (_powerPin >= 0) + { + powerOn(); + } + else + { + if (_printDebug == true) + _debugPort->println(F("modulePowerOn: not supported. _powerPin not defined.")); + } +} + +///////////// +// Private // +///////////// + +UBX_CELL_error_t SparkFun_ublox_Cellular::init(unsigned long baud, SparkFun_ublox_Cellular::UBX_CELL_init_type_t initType) +{ + int retries = _maxInitTries; + UBX_CELL_error_t err = UBX_CELL_ERROR_SUCCESS; + + beginSerial(baud); + + do + { + if (_printDebug == true) + _debugPort->println(F("init: Begin module init.")); + + if (initType == UBX_CELL_INIT_AUTOBAUD) + { + if (_printDebug == true) + _debugPort->println(F("init: Attempting autobaud connection to module.")); + + err = autobaud(baud); + + if (err != UBX_CELL_ERROR_SUCCESS) + { + initType = UBX_CELL_INIT_RESET; + } + } + else if (initType == UBX_CELL_INIT_RESET) + { + if (_printDebug == true) + _debugPort->println(F("init: Power cycling module.")); + + powerOff(); + delay(UBX_CELL_POWER_OFF_PULSE_PERIOD); + powerOn(); + beginSerial(baud); + delay(2000); + + err = at(); + if (err != UBX_CELL_ERROR_SUCCESS) + { + initType = UBX_CELL_INIT_AUTOBAUD; + } + } + if (err == UBX_CELL_ERROR_SUCCESS) + { + err = enableEcho(false); // = disableEcho + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + _debugPort->println(F("init: Module failed echo test.")); + initType = UBX_CELL_INIT_AUTOBAUD; + } + } + } while ((retries--) && (err != UBX_CELL_ERROR_SUCCESS)); + + // we tried but seems failed + if (err != UBX_CELL_ERROR_SUCCESS) + { + if (_printDebug == true) + _debugPort->println(F("init: Module failed to init. Exiting.")); + return (UBX_CELL_ERROR_NO_RESPONSE); + } + + if (_printDebug == true) + _debugPort->println(F("init: Module responded successfully.")); + + _baud = baud; + setGpioMode(GPIO1, NETWORK_STATUS); + // setGpioMode(GPIO2, GNSS_SUPPLY_ENABLE); + setGpioMode(GPIO6, TIME_PULSE_OUTPUT); + setSMSMessageFormat(UBX_CELL_MESSAGE_FORMAT_TEXT); + autoTimeZone(_autoTimeZoneForBegin); + for (int i = 0; i < UBX_CELL_NUM_SOCKETS; i++) + { + socketClose(i, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + } + + return UBX_CELL_ERROR_SUCCESS; +} + +void SparkFun_ublox_Cellular::invertPowerPin(bool invert) +{ + _invertPowerPin = invert; +} + +// Do a graceful power off. Hold the PWR_ON pin low for UBX_CELL_POWER_OFF_PULSE_PERIOD +// Note: +CPWROFF () is preferred to this. +void SparkFun_ublox_Cellular::powerOff(void) +{ + if (_powerPin >= 0) + { + if (_invertPowerPin) // Set the pin state before making it an output + digitalWrite(_powerPin, HIGH); + else + digitalWrite(_powerPin, LOW); + pinMode(_powerPin, OUTPUT); + if (_invertPowerPin) // Set the pin state + digitalWrite(_powerPin, HIGH); + else + digitalWrite(_powerPin, LOW); + delay(UBX_CELL_POWER_OFF_PULSE_PERIOD); + pinMode(_powerPin, INPUT); // Return to high-impedance, rely on (e.g.) SARA module internal pull-up + if (_printDebug == true) + _debugPort->println(F("powerOff: complete")); + } +} + +void SparkFun_ublox_Cellular::powerOn(void) +{ + if (_powerPin >= 0) + { + if (_invertPowerPin) // Set the pin state before making it an output + digitalWrite(_powerPin, HIGH); + else + digitalWrite(_powerPin, LOW); + pinMode(_powerPin, OUTPUT); + if (_invertPowerPin) // Set the pin state + digitalWrite(_powerPin, HIGH); + else + digitalWrite(_powerPin, LOW); + delay(UBX_CELL_POWER_ON_PULSE_PERIOD); + pinMode(_powerPin, INPUT); // Return to high-impedance, rely on (e.g.) SARA module internal pull-up + // delay(2000); // Do this in init. Wait before sending AT commands to module. 100 is too short. + if (_printDebug == true) + _debugPort->println(F("powerOn: complete")); + } +} + +// This does an abrupt emergency hardware shutdown of the SARA-R5 series modules. +// It only works if you have access to both the RESET_N and PWR_ON pins. +// You cannot use this function on the SparkFun Asset Tracker and RESET_N is tied to the MicroMod processor !RESET!... +void SparkFun_ublox_Cellular::hwReset(void) +{ + if ((_resetPin >= 0) && (_powerPin >= 0)) + { + digitalWrite(_resetPin, HIGH); // Start by making sure the RESET_N pin is high + pinMode(_resetPin, OUTPUT); + digitalWrite(_resetPin, HIGH); + + if (_invertPowerPin) // Now pull PWR_ON low - invert as necessary (on the Asset Tracker) + { + digitalWrite(_powerPin, HIGH); // Inverted - Asset Tracker + pinMode(_powerPin, OUTPUT); + digitalWrite(_powerPin, HIGH); + } + else + { + digitalWrite(_powerPin, LOW); // Not inverted + pinMode(_powerPin, OUTPUT); + digitalWrite(_powerPin, LOW); + } + + delay(UBX_CELL_RESET_PULSE_PERIOD); // Wait 23 seconds... (Yes, really!) + + digitalWrite(_resetPin, LOW); // Now pull RESET_N low + + delay(100); // Wait a little... (The data sheet doesn't say how long for) + + if (_invertPowerPin) // Now pull PWR_ON high - invert as necessary (on the Asset Tracker) + { + digitalWrite(_powerPin, LOW); // Inverted - Asset Tracker + } + else + { + digitalWrite(_powerPin, HIGH); // Not inverted + } + + delay(1500); // Wait 1.5 seconds + + digitalWrite(_resetPin, HIGH); // Now pull RESET_N high again + + pinMode(_resetPin, INPUT); // Return to high-impedance, rely on SARA module internal pull-up + pinMode(_powerPin, INPUT); // Return to high-impedance, rely on SARA module internal pull-up + } +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::functionality(UBX_CELL_functionality_t function) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_FUNC) + 16; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_COMMAND_FUNC, function); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_3_MIN_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::setMNOprofile(mobile_network_operator_t mno, bool autoReset, bool urcNotification) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_MNO) + 9; + char command[cmdLen]; + + if (mno == MNO_SIM_ICCID) // Only add autoReset and urcNotification if mno is MNO_SIM_ICCID + snprintf(command, cmdLen, "%s=%d,%d,%d", UBX_CELL_COMMAND_MNO, (uint8_t)mno, (uint8_t)autoReset, + (uint8_t)urcNotification); + else + snprintf(command, cmdLen, "%s=%d", UBX_CELL_COMMAND_MNO, (uint8_t)mno); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::getMNOprofile(mobile_network_operator_t *mno) +{ + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_MNO) + 2; + char command[cmdLen]; + char response[minimumResponseAllocation]; + mobile_network_operator_t o; + int d; + int r; + int u; + int oStore; + + snprintf(command, cmdLen, "%s?", UBX_CELL_COMMAND_MNO); + + err = sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, response, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + if (err != UBX_CELL_ERROR_SUCCESS) + return err; + + int scanned = 0; + char *searchPtr = strnstr(response, "+UMNOPROF:", minimumResponseAllocation); + if (searchPtr != nullptr) + { + searchPtr += strlen("+UMNOPROF:"); // Move searchPtr to first char + while (*searchPtr == ' ') + searchPtr++; // skip spaces + scanned = sscanf(searchPtr, "%d,%d,%d,%d", &oStore, &d, &r, &u); + } + o = (mobile_network_operator_t)oStore; + + if (scanned >= 1) + { + if (_printDebug == true) + { + _debugPort->print(F("getMNOprofile: MNO is: ")); + _debugPort->println(o); + } + *mno = o; + } + else + { + err = UBX_CELL_ERROR_INVALID; + } + + return err; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::waitForResponse(const char *expectedResponse, const char *expectedError, uint16_t timeout) +{ + unsigned long timeIn; + bool found = false; + bool error = false; + int responseIndex = 0, errorIndex = 0; + // bool printedSomething = false; + + timeIn = millis(); + + int responseLen = (int)strlen(expectedResponse); + int errorLen = (int)strlen(expectedError); + + while ((!found) && ((timeIn + timeout) > millis())) + { + if (hwAvailable() > 0) // hwAvailable can return -1 if the serial port is nullptr + { + char c = readChar(); + // if (_printDebug == true) + // { + // if (printedSomething == false) + // _debugPort->print(F("waitForResponse: ")); + // _debugPort->write(c); + // printedSomething = true; + // } + if ((responseIndex < responseLen) && (c == expectedResponse[responseIndex])) + { + if (++responseIndex == responseLen) + { + found = true; + } + } + else + { + responseIndex = ((responseIndex < responseLen) && (c == expectedResponse[0])) ? 1 : 0; + } + if ((errorIndex < errorLen) && (c == expectedError[errorIndex])) + { + if (++errorIndex == errorLen) + { + error = true; + found = true; + } + } + else + { + errorIndex = ((errorIndex < errorLen) && (c == expectedError[0])) ? 1 : 0; + } + //_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 nullptr 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; + } + } + else + { + yield(); + } + } + + // if (_printDebug == true) + // if (printedSomething) + // _debugPort->println(); + + pruneBacklog(); // Prune any incoming non-actionable URC's and responses/errors from the backlog + + if (found == true) + { + if (true == _printAtDebug) + { + _debugAtPort->print((error == true) ? expectedError : expectedResponse); + } + + return (error == true) ? UBX_CELL_ERROR_ERROR : UBX_CELL_ERROR_SUCCESS; + } + + return UBX_CELL_ERROR_NO_RESPONSE; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::sendCommandWithResponse(const char *command, const char *expectedResponse, + char *responseDest, unsigned long commandTimeout, int destSize, + bool at) +{ + bool found = false; + bool error = false; + int responseIndex = 0; + int errorIndex = 0; + int destIndex = 0; + unsigned int charsRead = 0; + int responseLen = 0; + int errorLen = 0; + const char *expectedError = nullptr; + bool printResponse = false; // Change to true to print the full response + bool printedSomething = false; + + if (_printDebug == true) + { + _debugPort->print(F("sendCommandWithResponse: Command: ")); + _debugPort->println(String(command)); + } + + sendCommand(command, at); // Sending command needs to dump data to backlog buffer as well. + unsigned long timeIn = millis(); + if (UBX_CELL_RESPONSE_OK_OR_ERROR == expectedResponse) + { + expectedResponse = UBX_CELL_RESPONSE_OK; + expectedError = UBX_CELL_RESPONSE_ERROR; + responseLen = sizeof(UBX_CELL_RESPONSE_OK) - 1; + errorLen = sizeof(UBX_CELL_RESPONSE_ERROR) - 1; + } + else + { + responseLen = (int)strlen(expectedResponse); + } + + while ((!found) && ((timeIn + commandTimeout) > millis())) + { + if (hwAvailable() > 0) // hwAvailable can return -1 if the serial port is nullptr + { + char c = readChar(); + if ((printResponse = true) && (_printDebug == true)) + { + if (printedSomething == false) + { + _debugPort->print(F("sendCommandWithResponse: Response: ")); + printedSomething = true; + } + _debugPort->write(c); + } + if (responseDest != nullptr) + { + 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 ((printResponse = true) && (printedSomething)) + _debugPort->println(); + _debugPort->print(F("sendCommandWithResponse: Panic! responseDest is full!")); + if ((printResponse = true) && (printedSomething)) + _debugPort->print(F("sendCommandWithResponse: Ignored response: ")); + } + } + } + charsRead++; + if ((errorIndex < errorLen) && (c == expectedError[errorIndex])) + { + if (++errorIndex == errorLen) + { + error = true; + found = true; + } + } + else + { + errorIndex = ((errorIndex < errorLen) && (c == expectedError[0])) ? 1 : 0; + } + if ((responseIndex < responseLen) && (c == expectedResponse[responseIndex])) + { + if (++responseIndex == responseLen) + { + found = true; + } + } + else + { + responseIndex = ((responseIndex < responseLen) && (c == expectedResponse[0])) ? 1 : 0; + } + //_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! + if (_saraResponseBacklogLength < _RXBuffSize) // Don't overflow the buffer + { + if (c == '\0') + _saraResponseBacklog[_saraResponseBacklogLength++] = '0'; // Change NULLs to ASCII Zeros + else + _saraResponseBacklog[_saraResponseBacklogLength++] = c; + } + } + else + { + yield(); + } + } + + if (_printDebug == true) + if ((printResponse = true) && (printedSomething)) + _debugPort->println(); + + pruneBacklog(); // Prune any incoming non-actionable URC's and responses/errors from the backlog + + if (found) + { + if ((true == _printAtDebug) && ((nullptr != responseDest) || (nullptr != expectedResponse))) + { + _debugAtPort->print((nullptr != responseDest) ? responseDest : expectedResponse); + } + return error ? UBX_CELL_ERROR_ERROR : UBX_CELL_ERROR_SUCCESS; + } + else if (charsRead == 0) + { + return UBX_CELL_ERROR_NO_RESPONSE; + } + else + { + if ((true == _printAtDebug) && (nullptr != responseDest)) + { + _debugAtPort->print(responseDest); + } + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } +} + +// Send a custom command with an expected (potentially partial) response, store entire response +UBX_CELL_error_t SparkFun_ublox_Cellular::sendCustomCommandWithResponse(const char *command, const char *expectedResponse, + char *responseDest, unsigned long commandTimeout, bool at) +{ + // Assume the user has allocated enough storage for any response. Set destSize to 32766. + return sendCommandWithResponse(command, expectedResponse, responseDest, commandTimeout, 32766, at); +} + +void SparkFun_ublox_Cellular::sendCommand(const char *command, bool at) +{ + // Check for incoming serial data. Copy it into the backlog + + // Important note: + // On ESP32, Serial.available only provides an update every ~120 bytes during the reception of long messages: + // https://gitter.im/espressif/arduino-esp32?at=5e25d6370a1cf54144909c85 + // Be aware that if a long message is being received, the code below will timeout after _rxWindowMillis = 2 millis. + // At 115200 baud, hwAvailable takes ~120 * 10 / 115200 = 10.4 millis before it indicates that data is being + // received. + + unsigned long timeIn = millis(); + if (hwAvailable() > 0) // hwAvailable can return -1 if the serial port is NULL + { + 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(); + 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(); + } + else + { + yield(); + } + } + } + + // Now send the command + if (at) + { + hwPrint(UBX_CELL_COMMAND_AT); + hwPrint(command); + hwPrint("\r\n"); + } + else + { + hwPrint(command); + } +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::parseSocketReadIndication(int socket, int length) +{ + UBX_CELL_error_t err; + char *readDest; + + if ((socket < 0) || (length < 0)) + { + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + // Return now if both callbacks pointers are nullptr - otherwise the data will be read and lost! + if ((_socketReadCallback == nullptr) && (_socketReadCallbackPlus == nullptr)) + return UBX_CELL_ERROR_INVALID; + + readDest = ubx_cell_calloc_char(length + 1); + if (readDest == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + + int bytesRead; + err = socketRead(socket, length, readDest, &bytesRead); + if (err != UBX_CELL_ERROR_SUCCESS) + { + free(readDest); + return err; + } + + if (_socketReadCallback != nullptr) + { + String dataAsString = ""; // Create an empty string + // Copy the data from readDest into the String in a binary-compatible way + // Important Note: some implementations of concat, like the one on ESP32, are binary-compatible. + // But some, like SAMD, are not. They use strlen or strcpy internally - which don't like \0's. + // The only true binary-compatible solution is to use socketReadCallbackPlus... + for (int i = 0; i < bytesRead; i++) + dataAsString.concat(readDest[i]); + _socketReadCallback(socket, dataAsString); + } + + if (_socketReadCallbackPlus != nullptr) + { + IPAddress dummyAddress = {0, 0, 0, 0}; + int dummyPort = 0; + _socketReadCallbackPlus(socket, (const char *)readDest, bytesRead, dummyAddress, dummyPort); + } + + free(readDest); + return UBX_CELL_ERROR_SUCCESS; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::parseSocketReadIndicationUDP(int socket, int length) +{ + UBX_CELL_error_t err; + char *readDest; + IPAddress remoteAddress = {0, 0, 0, 0}; + int remotePort = 0; + + if ((socket < 0) || (length < 0)) + { + return UBX_CELL_ERROR_UNEXPECTED_RESPONSE; + } + + // Return now if both callbacks pointers are nullptr - otherwise the data will be read and lost! + if ((_socketReadCallback == nullptr) && (_socketReadCallbackPlus == nullptr)) + return UBX_CELL_ERROR_INVALID; + + readDest = ubx_cell_calloc_char(length + 1); + if (readDest == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + + int bytesRead; + err = socketReadUDP(socket, length, readDest, &remoteAddress, &remotePort, &bytesRead); + if (err != UBX_CELL_ERROR_SUCCESS) + { + free(readDest); + return err; + } + + if (_socketReadCallback != nullptr) + { + String dataAsString = ""; // Create an empty string + // Important Note: some implementations of concat, like the one on ESP32, are binary-compatible. + // But some, like SAMD, are not. They use strlen or strcpy internally - which don't like \0's. + // The only true binary-compatible solution is to use socketReadCallbackPlus... + for (int i = 0; i < bytesRead; i++) // Copy the data from readDest into the String in a binary-compatible way + dataAsString.concat(readDest[i]); + _socketReadCallback(socket, dataAsString); + } + + if (_socketReadCallbackPlus != nullptr) + { + _socketReadCallbackPlus(socket, (const char *)readDest, bytesRead, remoteAddress, remotePort); + } + + free(readDest); + return UBX_CELL_ERROR_SUCCESS; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::parseSocketListenIndication(int listeningSocket, IPAddress localIP, + unsigned int listeningPort, int socket, IPAddress remoteIP, + unsigned int port) +{ + _lastLocalIP = localIP; + _lastRemoteIP = remoteIP; + + if (_socketListenCallback != nullptr) + { + _socketListenCallback(listeningSocket, localIP, listeningPort, socket, remoteIP, port); + } + + return UBX_CELL_ERROR_SUCCESS; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::parseSocketCloseIndication(String *closeIndication) +{ + int search; + int socket; + + search = closeIndication->indexOf(UBX_CELL_CLOSE_SOCKET_URC); + search += strlen(UBX_CELL_CLOSE_SOCKET_URC); + while (closeIndication->charAt(search) == ' ') + search++; // skip spaces + + // Socket will be first integer, should be single-digit number between 0-6: + socket = closeIndication->substring(search, search + 1).toInt(); + + if (_socketCloseCallback != nullptr) + { + _socketCloseCallback(socket); + } + + return UBX_CELL_ERROR_SUCCESS; +} + +size_t SparkFun_ublox_Cellular::hwPrint(const char *s) +{ + if ((true == _printAtDebug) && (nullptr != s)) + { + _debugAtPort->print(s); + } + if (_hardSerial != nullptr) + { + return _hardSerial->print(s); + } +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + else if (_softSerial != nullptr) + { + return _softSerial->print(s); + } +#endif + + return (size_t)0; +} + +size_t SparkFun_ublox_Cellular::hwWriteData(const char *buff, int len) +{ + if ((true == _printAtDebug) && (nullptr != buff) && (0 < len)) + { + _debugAtPort->write(buff, len); + } + if (_hardSerial != nullptr) + { + return _hardSerial->write((const uint8_t *)buff, len); + } +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + else if (_softSerial != nullptr) + { + return _softSerial->write((const uint8_t *)buff, len); + } +#endif + return (size_t)0; +} + +size_t SparkFun_ublox_Cellular::hwWrite(const char c) +{ + if (true == _printAtDebug) + { + _debugAtPort->write(c); + } + if (_hardSerial != nullptr) + { + return _hardSerial->write(c); + } +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + else if (_softSerial != nullptr) + { + return _softSerial->write(c); + } +#endif + + return (size_t)0; +} + +int SparkFun_ublox_Cellular::readAvailable(char *inString) +{ + int len = 0; + + if (_hardSerial != nullptr) + { + while (_hardSerial->available()) + { + char c = (char)_hardSerial->read(); + if (inString != nullptr) + { + inString[len++] = c; + } + } + if (inString != nullptr) + { + inString[len] = 0; + } + // if (_printDebug == true) + // _debugPort->println(inString); + } +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + else if (_softSerial != nullptr) + { + while (_softSerial->available()) + { + char c = (char)_softSerial->read(); + if (inString != nullptr) + { + inString[len++] = c; + } + } + if (inString != nullptr) + { + inString[len] = 0; + } + } +#endif + + return len; +} + +char SparkFun_ublox_Cellular::readChar(void) +{ + char ret = 0; + + if (_hardSerial != nullptr) + { + ret = (char)_hardSerial->read(); + } +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + else if (_softSerial != nullptr) + { + ret = (char)_softSerial->read(); + } +#endif + + return ret; +} + +int SparkFun_ublox_Cellular::hwAvailable(void) +{ + if (_hardSerial != nullptr) + { + return _hardSerial->available(); + } +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + else if (_softSerial != nullptr) + { + return _softSerial->available(); + } +#endif + + return -1; +} + +void SparkFun_ublox_Cellular::beginSerial(unsigned long baud) +{ + delay(100); + if (_hardSerial != nullptr) + { + _hardSerial->end(); + _hardSerial->begin(baud); + } +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + else if (_softSerial != nullptr) + { + _softSerial->end(); + _softSerial->begin(baud); + } +#endif + delay(100); +} + +void SparkFun_ublox_Cellular::setTimeout(unsigned long timeout) +{ + if (_hardSerial != nullptr) + { + _hardSerial->setTimeout(timeout); + } +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + else if (_softSerial != nullptr) + { + _softSerial->setTimeout(timeout); + } +#endif +} + +bool SparkFun_ublox_Cellular::find(char *target) +{ + bool found = false; + if (_hardSerial != nullptr) + { + found = _hardSerial->find(target); + } +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + else if (_softSerial != nullptr) + { + found = _softSerial->find(target); + } +#endif + return found; +} + +UBX_CELL_error_t SparkFun_ublox_Cellular::autobaud(unsigned long desiredBaud) +{ + UBX_CELL_error_t err = UBX_CELL_ERROR_INVALID; + int b = 0; + + while ((err != UBX_CELL_ERROR_SUCCESS) && (b < NUM_SUPPORTED_BAUD)) + { + beginSerial(UBX_CELL_SUPPORTED_BAUD[b++]); + setBaud(desiredBaud); + beginSerial(desiredBaud); + err = at(); + } + if (err == UBX_CELL_ERROR_SUCCESS) + { + beginSerial(desiredBaud); + } + return err; +} + +char *SparkFun_ublox_Cellular::ubx_cell_calloc_char(size_t num) +{ + return (char *)calloc(num, sizeof(char)); +} + +// This prunes the backlog of non-actionable events. If new actionable events are added, you must modify the if +// statement. +void SparkFun_ublox_Cellular::pruneBacklog() +{ + char *event; + + // 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")); + // } + // } + + memset(_pruneBuffer, 0, _RXBuffSize); // Clear the _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 != nullptr) // If event is actionable, add it to pruneBuffer. + { + // These are the events we want to keep so they can be processed by poll / bufferedPoll + for (auto urcString : _urcStrings) + { + if (strnstr(event, urcString, _RXBuffSize - (event - _saraResponseBacklog)) != nullptr) + { + 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 + break; // No need to check any other events + } + } + + event = strtok_r(nullptr, "\r\n", &preservedEvent); // Walk though any remaining events + } + + memset(_saraResponseBacklog, 0, _RXBuffSize); // Clear out backlog buffer. + memcpy(_saraResponseBacklog, _pruneBuffer, + _saraResponseBacklogLength); // Copy the pruned buffer back into the backlog + + // 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")); + // } + // } +} + +// GPS Helper Functions: + +// Read a source string until a delimiter is hit, store the result in destination +char *SparkFun_ublox_Cellular::readDataUntil(char *destination, unsigned int destSize, char *source, char delimiter) +{ + + char *strEnd; + size_t len; + + strEnd = strchr(source, delimiter); + + if (strEnd != nullptr) + { + len = strEnd - source; + memset(destination, 0, destSize); + memcpy(destination, source, len); + } + + return strEnd; +} + +bool SparkFun_ublox_Cellular::parseGPRMCString(char *rmcString, PositionData *pos, ClockData *clk, SpeedData *spd) +{ + char *ptr, *search; + unsigned long tTemp; + char tempData[TEMP_NMEA_DATA_SIZE]; + + // if (_printDebug == true) + // { + // _debugPort->println(F("parseGPRMCString: rmcString: ")); + // _debugPort->println(rmcString); + // } + + // Fast-forward test to first value: + ptr = strchr(rmcString, ','); + ptr++; // Move ptr past first comma + + // If the next character is another comma, there's no time data + // Find time: + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + // Next comma should be present and not the next position + if ((search != nullptr) && (search != ptr)) + { + pos->utc = atof(tempData); // Extract hhmmss.ss as float + tTemp = pos->utc; // Convert to unsigned long (discard the digits beyond the decimal point) + clk->time.ms = ((unsigned int)(pos->utc * 100)) % 100; // Extract the milliseconds + clk->time.hour = tTemp / 10000; + tTemp -= ((unsigned long)clk->time.hour * 10000); + clk->time.minute = tTemp / 100; + tTemp -= ((unsigned long)clk->time.minute * 100); + clk->time.second = tTemp; + } + else + { + pos->utc = 0.0; + clk->time.hour = 0; + clk->time.minute = 0; + clk->time.second = 0; + } + ptr = search + 1; // Move pointer to next value + + // Find status character: + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + // Should be a single character: V = Data invalid, A = Data valid + if ((search != nullptr) && (search == ptr + 1)) + { + pos->status = *ptr; // Assign char at ptr to status + } + else + { + pos->status = 'X'; // Made up very bad status + } + ptr = search + 1; + + // Find latitude: + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + if ((search != nullptr) && (search != ptr)) + { + pos->lat = atof(tempData); // Extract ddmm.mmmmm as float + unsigned long lat_deg = pos->lat / 100; // Extract the degrees + pos->lat -= (float)lat_deg * 100.0; // Subtract the degrees leaving only the minutes + pos->lat /= 60.0; // Convert minutes into degrees + pos->lat += (float)lat_deg; // Finally add the degrees back on again + } + else + { + pos->lat = 0.0; + } + ptr = search + 1; + + // Find latitude hemishpere + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + if ((search != nullptr) && (search == ptr + 1)) + { + if (*ptr == 'S') // Is the latitude South + pos->lat *= -1.0; // Make lat negative + } + ptr = search + 1; + + // Find longitude: + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + if ((search != nullptr) && (search != ptr)) + { + pos->lon = atof(tempData); // Extract dddmm.mmmmm as float + unsigned long lon_deg = pos->lon / 100; // Extract the degrees + pos->lon -= (float)lon_deg * 100.0; // Subtract the degrees leaving only the minutes + pos->lon /= 60.0; // Convert minutes into degrees + pos->lon += (float)lon_deg; // Finally add the degrees back on again + } + else + { + pos->lon = 0.0; + } + ptr = search + 1; + + // Find longitude hemishpere + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + if ((search != nullptr) && (search == ptr + 1)) + { + if (*ptr == 'W') // Is the longitude West + pos->lon *= -1.0; // Make lon negative + } + ptr = search + 1; + + // Find speed + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + if ((search != nullptr) && (search != ptr)) + { + spd->speed = atof(tempData); // Extract speed over ground in knots + spd->speed *= 0.514444; // Convert to m/s + } + else + { + spd->speed = 0.0; + } + ptr = search + 1; + + // Find course over ground + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + if ((search != nullptr) && (search != ptr)) + { + spd->cog = atof(tempData); + } + else + { + spd->cog = 0.0; + } + ptr = search + 1; + + // Find date + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + if ((search != nullptr) && (search != ptr)) + { + tTemp = atol(tempData); + clk->date.day = tTemp / 10000; + tTemp -= ((unsigned long)clk->date.day * 10000); + clk->date.month = tTemp / 100; + tTemp -= ((unsigned long)clk->date.month * 100); + clk->date.year = tTemp; + } + else + { + clk->date.day = 0; + clk->date.month = 0; + clk->date.year = 0; + } + ptr = search + 1; + + // Find magnetic variation in degrees: + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + if ((search != nullptr) && (search != ptr)) + { + spd->magVar = atof(tempData); + } + else + { + spd->magVar = 0.0; + } + ptr = search + 1; + + // Find magnetic variation direction + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, ','); + if ((search != nullptr) && (search == ptr + 1)) + { + if (*ptr == 'W') // Is the magnetic variation West + spd->magVar *= -1.0; // Make magnetic variation negative + } + ptr = search + 1; + + // Find position system mode + // Possible values for posMode: N = No fix, E = Estimated/Dead reckoning fix, A = Autonomous GNSS fix, + // D = Differential GNSS fix, F = RTK float, R = RTK fixed + search = readDataUntil(tempData, TEMP_NMEA_DATA_SIZE, ptr, '*'); + if ((search != nullptr) && (search = ptr + 1)) + { + pos->mode = *ptr; + } + else + { + pos->mode = 'X'; + } + ptr = search + 1; + + if (pos->status == 'A') + { + return true; + } + return false; +} diff --git a/src/sfe_ublox_cellular.h b/src/sfe_ublox_cellular.h new file mode 100644 index 0000000..9e391d6 --- /dev/null +++ b/src/sfe_ublox_cellular.h @@ -0,0 +1,1194 @@ +/* + Arduino library for the u-blox SARA-R5 LTE-M / NB-IoT modules with secure cloud, as used on the SparkFun MicroMod + Asset Tracker By: Paul Clark October 19th 2020 + + Based extensively on the: + Arduino Library for the SparkFun LTE CAT M1/NB-IoT Shield - SARA-R4 + Written by Jim Lindblom @ SparkFun Electronics, September 5, 2018 + + This Arduino library provides mechanisms to initialize and use + the SARA-R5 module over either a SoftwareSerial or hardware serial port. + + Please see LICENSE.md for the license information + +*/ + +#ifndef SPARKFUN_UBX_CELL_ARDUINO_LIBRARY_H +#define SPARKFUN_UBX_CELL_ARDUINO_LIBRARY_H + +#if (ARDUINO >= 100) +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#ifdef ARDUINO_ARCH_AVR // Arduino AVR boards (Uno, Pro Micro, etc.) +#define UBX_CELL_SOFTWARE_SERIAL_ENABLED // Enable software serial +#endif + +#ifdef ARDUINO_ARCH_SAMD // Arduino SAMD boards (SAMD21, etc.) +#define UBX_CELL_SOFTWARE_SERIAL_ENABLEDx // Disable software serial +#endif + +#ifdef ARDUINO_ARCH_APOLLO3 // Arduino Apollo boards (Artemis module, RedBoard Artemis, etc) +#define UBX_CELL_SOFTWARE_SERIAL_ENABLEDx // Disable software serial (no longer supported with v2 of Apollo3) +// Note: paulvha has provided software serial support for v2 of the Apollo3 / Artemis core. +// Further details are available at: +// https://github.com/paulvha/apollo3/tree/master/SoftwareSerial +#endif + +#ifdef ARDUINO_ARCH_STM32 // STM32 based boards (Disco, Nucleo, etc) +#define UBX_CELL_SOFTWARE_SERIAL_ENABLED // Enable software serial +#endif + +#ifdef ARDUINO_ARCH_ESP32 // ESP32 based boards +// Check to see if ESP Software Serial has been included +// Note: you need to #include at the very start of your script, +// _before_ the #include , for this to work. +// See SARA-R5_Example2_Identification_ESPSoftwareSerial for more details. +#if __has_include( ) +#define UBX_CELL_SOFTWARE_SERIAL_ENABLED // Enable software serial +#else +#define UBX_CELL_SOFTWARE_SERIAL_ENABLEDx // Disable software serial +#endif +#endif + +#ifdef ARDUINO_ARCH_ESP8266 // ESP8266 based boards +// Check to see if ESP Software Serial has been included +// Note: you need to #include at the very start of your script, +// _before_ the #include , for this to work. +// See SARA-R5_Example2_Identification_ESPSoftwareSerial for more details. +#if __has_include( ) +#define UBX_CELL_SOFTWARE_SERIAL_ENABLED // Enable software serial +#else +#define UBX_CELL_SOFTWARE_SERIAL_ENABLEDx // Disable software serial +#endif +#endif + +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED +#include // SoftwareSerial.h is guarded. It is OK to include it twice. +#endif + +#include +#include + +#define UBX_CELL_POWER_PIN -1 // Default to no pin +#define UBX_CELL_RESET_PIN -1 + +// Timing +#define UBX_CELL_STANDARD_RESPONSE_TIMEOUT 1000 +#define UBX_CELL_10_SEC_TIMEOUT 10000 +#define UBX_CELL_55_SECS_TIMEOUT 55000 +#define UBX_CELL_2_MIN_TIMEOUT 120000 +#define UBX_CELL_3_MIN_TIMEOUT 180000 +#define UBX_CELL_SET_BAUD_TIMEOUT 500 +#define UBX_CELL_POWER_OFF_PULSE_PERIOD 3200 // Hold PWR_ON low for this long to power the module off +#define UBX_CELL_POWER_ON_PULSE_PERIOD 100 // Hold PWR_ON low for this long to power the module on (SARA-R510M8S) +#define UBX_CELL_RESET_PULSE_PERIOD \ + 23000 // Used to perform an abrupt emergency hardware shutdown. 23 seconds... (Yes, really!) +#define UBX_CELL_POWER_OFF_TIMEOUT 40000 // Datasheet says 40 seconds... +#define UBX_CELL_IP_CONNECT_TIMEOUT 130000 +#define UBX_CELL_POLL_DELAY 1 +#define UBX_CELL_SOCKET_WRITE_TIMEOUT 10000 + +// ## Suported AT Commands +// ### General +const char *const UBX_CELL_COMMAND_AT = "AT"; // AT "Test" +const char *const UBX_CELL_COMMAND_ECHO = "E"; // Local Echo +const char *const UBX_CELL_COMMAND_MANU_ID = "+CGMI"; // Manufacturer identification +const char *const UBX_CELL_COMMAND_MODEL_ID = "+CGMM"; // Model identification +const char *const UBX_CELL_COMMAND_FW_VER_ID = "+CGMR"; // Firmware version identification +const char *const UBX_CELL_COMMAND_SERIAL_NO = "+CGSN"; // Product serial number +const char *const UBX_CELL_COMMAND_IMEI = "+GSN"; // IMEI identification +const char *const UBX_CELL_COMMAND_IMSI = "+CIMI"; // IMSI identification +const char *const UBX_CELL_COMMAND_CCID = "+CCID"; // SIM CCID +const char *const UBX_CELL_COMMAND_REQ_CAP = "+GCAP"; // Request capabilities list +// ### Control and status +const char *const UBX_CELL_COMMAND_POWER_OFF = "+CPWROFF"; // Module switch off +const char *const UBX_CELL_COMMAND_FUNC = "+CFUN"; // Functionality (reset, etc.) +const char *const UBX_CELL_COMMAND_CLOCK = "+CCLK"; // Real-time clock +const char *const UBX_CELL_COMMAND_AUTO_TZ = "+CTZU"; // Automatic time zone update +const char *const UBX_CELL_COMMAND_TZ_REPORT = "+CTZR"; // Time zone reporting +// ### Network service +const char *const UBX_CELL_COMMAND_CNUM = "+CNUM"; // Subscriber number +const char *const UBX_CELL_SIGNAL_QUALITY = "+CSQ"; +const char *const UBX_CELL_EXT_SIGNAL_QUALITY = "+CESQ"; +const char *const UBX_CELL_OPERATOR_SELECTION = "+COPS"; +const char *const UBX_CELL_REGISTRATION_STATUS = "+CREG"; +const char *const UBX_CELL_EPSREGISTRATION_STATUS = "+CEREG"; +const char *const UBX_CELL_READ_OPERATOR_NAMES = "+COPN"; +const char *const UBX_CELL_COMMAND_MNO = "+UMNOPROF"; // MNO (mobile network operator) Profile +// ### SIM +const char *const UBX_CELL_SIM_STATE = "+USIMSTAT"; +const char *const UBX_CELL_COMMAND_SIMPIN = "+CPIN"; // SIM PIN +// ### SMS +const char *const UBX_CELL_MESSAGE_FORMAT = "+CMGF"; // Set SMS message format +const char *const UBX_CELL_SEND_TEXT = "+CMGS"; // Send SMS message +const char *const UBX_CELL_NEW_MESSAGE_IND = "+CNMI"; // New [SMS] message indication +const char *const UBX_CELL_PREF_MESSAGE_STORE = "+CPMS"; // Preferred message storage +const char *const UBX_CELL_READ_TEXT_MESSAGE = "+CMGR"; // Read message +const char *const UBX_CELL_DELETE_MESSAGE = "+CMGD"; // Delete message +// V24 control and V25ter (UART interface) +const char *const UBX_CELL_FLOW_CONTROL = "&K"; // Flow control +const char *const UBX_CELL_COMMAND_BAUD = "+IPR"; // Baud rate +// ### Packet switched data services +const char *const UBX_CELL_MESSAGE_PDP_DEF = "+CGDCONT"; // Packet switched Data Profile context definition +const char *const UBX_CELL_MESSAGE_PDP_CONTEXT_ACTIVATE = + "+CGACT"; // Activates or deactivates the specified PDP context +const char *const UBX_CELL_MESSAGE_ENTER_PPP = "D"; +// ### GPIO +const char *const UBX_CELL_COMMAND_GPIO = "+UGPIOC"; // GPIO Configuration +// ### IP +const char *const UBX_CELL_CREATE_SOCKET = "+USOCR"; // Create a new socket +const char *const UBX_CELL_CLOSE_SOCKET = "+USOCL"; // Close a socket +const char *const UBX_CELL_CONNECT_SOCKET = "+USOCO"; // Connect to server on socket +const char *const UBX_CELL_WRITE_SOCKET = "+USOWR"; // Write data to a socket +const char *const UBX_CELL_WRITE_UDP_SOCKET = "+USOST"; // Write data to a UDP socket +const char *const UBX_CELL_READ_SOCKET = "+USORD"; // Read from a socket +const char *const UBX_CELL_READ_UDP_SOCKET = "+USORF"; // Read UDP data from a socket +const char *const UBX_CELL_LISTEN_SOCKET = "+USOLI"; // Listen for connection on socket +const char *const UBX_CELL_GET_ERROR = "+USOER"; // Get last socket error. +const char *const UBX_CELL_SOCKET_DIRECT_LINK = "+USODL"; // Set socket in Direct Link mode +const char *const UBX_CELL_SOCKET_CONTROL = "+USOCTL"; // Query the socket parameters +const char *const UBX_CELL_UD_CONFIGURATION = "+UDCONF"; // User Datagram Configuration +// ### Ping +const char *const UBX_CELL_PING_COMMAND = "+UPING"; // Ping +// ### HTTP +const char *const UBX_CELL_HTTP_PROFILE = + "+UHTTP"; // Configure the HTTP profile. Up to 4 different profiles can be defined +const char *const UBX_CELL_HTTP_COMMAND = "+UHTTPC"; // Trigger the specified HTTP command +const char *const UBX_CELL_HTTP_PROTOCOL_ERROR = + "+UHTTPER"; // Retrieves the error class and code of the latest HTTP operation on the specified HTTP profile. + +const char *const UBX_CELL_MQTT_NVM = "+UMQTTNV"; +const char *const UBX_CELL_MQTT_PROFILE = "+UMQTT"; +const char *const UBX_CELL_MQTT_COMMAND = "+UMQTTC"; +const char *const UBX_CELL_MQTT_PROTOCOL_ERROR = "+UMQTTER"; +// ### FTP +const char *const UBX_CELL_FTP_PROFILE = "+UFTP"; +const char *const UBX_CELL_FTP_COMMAND = "+UFTPC"; +const char *const UBX_CELL_FTP_PROTOCOL_ERROR = "+UFTPER"; +// ### GNSS +const char *const UBX_CELL_GNSS_POWER = "+UGPS"; // GNSS power management configuration +const char *const UBX_CELL_GNSS_ASSISTED_IND = "+UGIND"; // Assisted GNSS unsolicited indication +const char *const UBX_CELL_GNSS_REQUEST_LOCATION = "+ULOC"; // Ask for localization information +const char *const UBX_CELL_GNSS_GPRMC = "+UGRMC"; // Ask for localization information +const char *const UBX_CELL_GNSS_CONFIGURE_SENSOR = "+ULOCGNSS"; // Configure GNSS sensor +const char *const UBX_CELL_GNSS_CONFIGURE_LOCATION = "+ULOCCELL"; // Configure cellular location sensor (CellLocate®) +const char *const UBX_CELL_AIDING_SERVER_CONFIGURATION = "+UGSRV"; // Configure aiding server (CellLocate®) +// ### File System +// TO DO: Add support for file tags. Default tag to USER +const char *const UBX_CELL_FILE_SYSTEM_READ_FILE = "+URDFILE"; // Read a file +const char *const UBX_CELL_FILE_SYSTEM_READ_BLOCK = "+URDBLOCK"; // Read a block from a file +const char *const UBX_CELL_FILE_SYSTEM_DOWNLOAD_FILE = "+UDWNFILE"; // Download a file into the module +const char *const UBX_CELL_FILE_SYSTEM_LIST_FILES = "+ULSTFILE"; // List of files, size of file, etc. +const char *const UBX_CELL_FILE_SYSTEM_DELETE_FILE = "+UDELFILE"; // Delete a file +// ### File System +// TO DO: Add support for file tags. Default tag to USER +const char *const UBX_CELL_SEC_PROFILE = "+USECPRF"; +const char *const UBX_CELL_SEC_MANAGER = "+USECMNG"; + +// ### URC strings +const char *const UBX_CELL_READ_SOCKET_URC = "+UUSORD:"; +const char *const UBX_CELL_READ_UDP_SOCKET_URC = "+UUSORF:"; +const char *const UBX_CELL_LISTEN_SOCKET_URC = "+UUSOLI:"; +const char *const UBX_CELL_CLOSE_SOCKET_URC = "+UUSOCL:"; +const char *const UBX_CELL_GNSS_REQUEST_LOCATION_URC = "+UULOC:"; +const char *const UBX_CELL_SIM_STATE_URC = "+UUSIMSTAT:"; +const char *const UBX_CELL_HTTP_COMMAND_URC = "+UUHTTPCR:"; +const char *const UBX_CELL_MQTT_COMMAND_URC = "+UUMQTTC:"; +const char *const UBX_CELL_PING_COMMAND_URC = "+UUPING:"; +const char *const UBX_CELL_REGISTRATION_STATUS_URC = "+CREG:"; +const char *const UBX_CELL_EPSREGISTRATION_STATUS_URC = "+CEREG:"; +const char *const UBX_CELL_FTP_COMMAND_URC = "+UUFTPCR:"; + +// ### Response +const char *const UBX_CELL_RESPONSE_MORE = "\n>"; +const char *const UBX_CELL_RESPONSE_OK = "\nOK\r\n"; +const char *const UBX_CELL_RESPONSE_ERROR = "\nERROR\r\n"; +const char *const UBX_CELL_RESPONSE_CONNECT = "\r\nCONNECT\r\n"; +#define UBX_CELL_RESPONSE_OK_OR_ERROR nullptr + +// URC handler type definition +typedef std::function UBX_CELL_urc_handler_t; + +// CTRL+Z and ESC ASCII codes for SMS message sends +const char ASCII_CTRL_Z = 0x1A; +const char ASCII_ESC = 0x1B; + +// NMEA data size - used by parseGPRMCString +#define TEMP_NMEA_DATA_SIZE 16 + +#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 UBX_CELL_NUM_SOCKETS 6 + +#define NUM_SUPPORTED_BAUD 6 +const unsigned long UBX_CELL_SUPPORTED_BAUD[NUM_SUPPORTED_BAUD] = {115200, 9600, 19200, 38400, 57600, 230400}; +#define UBX_CELL_DEFAULT_BAUD_RATE 115200 + +// Flow control definitions for AT&K +// Note: SW (XON/XOFF) flow control is not supported on the UBX_CELL +typedef enum +{ + UBX_CELL_DISABLE_FLOW_CONTROL = 0, + UBX_CELL_ENABLE_FLOW_CONTROL = 3 +} UBX_CELL_flow_control_t; + +// The standard Europe profile should be used as the basis for all other MNOs in Europe outside of Vodafone +// and Deutsche Telekom. However, there may be changes that need to be applied to the module for proper +// operation with any given European MNO such as attach type, RAT preference, band selection, etc. Please +// consult with the preferred network provider. +typedef enum +{ + MNO_INVALID = -1, + MNO_SW_DEFAULT = 0, // Undefined / regulatory + MNO_SIM_ICCID = 1, + MNO_ATT = 2, // AT&T + MNO_VERIZON = 3, + MNO_TELSTRA = 4, + MNO_TMO = 5, // T-Mobile US + MNO_CT = 6, // China Telecom + MNO_SPRINT = 8, + MNO_VODAFONE = 19, + MNO_NTT_DOCOMO = 20, + MNO_TELUS = 21, + MNO_SOFTBANK = 28, + MNO_DT = 31, // Deutsche Telekom + MNO_US_CELLULAR = 32, + MNO_SKT = 39, + MNO_GLOBAL = 90, + MNO_STD_EUROPE = 100, + MNO_STD_EU_NOEPCO = 101 +} mobile_network_operator_t; + +typedef enum +{ + UBX_CELL_ERROR_INVALID = -1, // -1 + UBX_CELL_ERROR_SUCCESS = 0, // 0 + UBX_CELL_ERROR_OUT_OF_MEMORY, // 1 + UBX_CELL_ERROR_TIMEOUT, // 2 + UBX_CELL_ERROR_UNEXPECTED_PARAM, // 3 + UBX_CELL_ERROR_UNEXPECTED_RESPONSE, // 4 + UBX_CELL_ERROR_NO_RESPONSE, // 5 + UBX_CELL_ERROR_DEREGISTERED, // 6 + UBX_CELL_ERROR_ZERO_READ_LENGTH, // 7 + UBX_CELL_ERROR_ERROR // 8 +} UBX_CELL_error_t; +#define UBX_CELL_SUCCESS UBX_CELL_ERROR_SUCCESS + +typedef enum +{ + UBX_CELL_REGISTRATION_INVALID = -1, + UBX_CELL_REGISTRATION_NOT_REGISTERED = 0, + UBX_CELL_REGISTRATION_HOME = 1, + UBX_CELL_REGISTRATION_SEARCHING = 2, + UBX_CELL_REGISTRATION_DENIED = 3, + UBX_CELL_REGISTRATION_UNKNOWN = 4, + UBX_CELL_REGISTRATION_ROAMING = 5, + UBX_CELL_REGISTRATION_HOME_SMS_ONLY = 6, + UBX_CELL_REGISTRATION_ROAMING_SMS_ONLY = 7, + UBX_CELL_REGISTRATION_EMERGENCY_SERV_ONLY = 8, + UBX_CELL_REGISTRATION_HOME_CSFB_NOT_PREFERRED = 9, + UBX_CELL_REGISTRATION_ROAMING_CSFB_NOT_PREFERRED = 10 +} UBX_CELL_registration_status_t; + +struct DateData +{ + uint8_t day; + uint8_t month; + unsigned int year; +}; + +struct TimeData +{ + uint8_t hour; + uint8_t minute; + uint8_t second; + unsigned int ms; + uint8_t tzh; + uint8_t tzm; +}; + +struct ClockData +{ + struct DateData date; + struct TimeData time; +}; + +struct PositionData +{ + float utc; + float lat; // Degrees: +/- 90 + float lon; // Degrees: +/- 180 + float alt; + char mode; + char status; +}; + +struct SpeedData +{ + float speed; // m/s + float cog; // Degrees + float magVar; // Degrees +}; + +struct operator_stats +{ + uint8_t stat; + String shortOp; + String longOp; + unsigned long numOp; + uint8_t act; +}; + +typedef struct ext_signal_quality_ +{ + unsigned int rxlev; + unsigned int ber; + unsigned int rscp; + unsigned int enc0; + unsigned int rsrq; + unsigned int rsrp; +} signal_quality; + +typedef enum +{ + UBX_CELL_TCP = 6, + UBX_CELL_UDP = 17 +} UBX_CELL_socket_protocol_t; + +typedef enum +{ + UBX_CELL_TCP_SOCKET_STATUS_INACTIVE, + UBX_CELL_TCP_SOCKET_STATUS_LISTEN, + UBX_CELL_TCP_SOCKET_STATUS_SYN_SENT, + UBX_CELL_TCP_SOCKET_STATUS_SYN_RCVD, + UBX_CELL_TCP_SOCKET_STATUS_ESTABLISHED, + UBX_CELL_TCP_SOCKET_STATUS_FIN_WAIT_1, + UBX_CELL_TCP_SOCKET_STATUS_FIN_WAIT_2, + UBX_CELL_TCP_SOCKET_STATUS_CLOSE_WAIT, + UBX_CELL_TCP_SOCKET_STATUS_CLOSING, + UBX_CELL_TCP_SOCKET_STATUS_LAST_ACK, + UBX_CELL_TCP_SOCKET_STATUS_TIME_WAIT +} UBX_CELL_tcp_socket_status_t; + +typedef enum +{ + UBX_CELL_MESSAGE_FORMAT_PDU = 0, + UBX_CELL_MESSAGE_FORMAT_TEXT = 1 +} UBX_CELL_message_format_t; + +typedef enum +{ + UBX_CELL_UTIME_MODE_STOP = 0, + UBX_CELL_UTIME_MODE_PPS, + UBX_CELL_UTIME_MODE_ONE_SHOT, + UBX_CELL_UTIME_MODE_EXT_INT +} UBX_CELL_utime_mode_t; + +typedef enum +{ + UBX_CELL_UTIME_SENSOR_NONE = 0, + UBX_CELL_UTIME_SENSOR_GNSS_LTE = 1, + UBX_CELL_UTIME_SENSOR_LTE +} UBX_CELL_utime_sensor_t; + +typedef enum +{ + UBX_CELL_UTIME_URC_CONFIGURATION_DISABLED = 0, + UBX_CELL_UTIME_URC_CONFIGURATION_ENABLED +} UBX_CELL_utime_urc_configuration_t; + +typedef enum +{ + UBX_CELL_SIM_NOT_PRESENT = 0, + UBX_CELL_SIM_PIN_NEEDED, + UBX_CELL_SIM_PIN_BLOCKED, + UBX_CELL_SIM_PUK_BLOCKED, + UBX_CELL_SIM_NOT_OPERATIONAL, + UBX_CELL_SIM_RESTRICTED, + UBX_CELL_SIM_OPERATIONAL + // UBX_CELL_SIM_PHONEBOOK_READY, // Not reported by SARA-R5 + // UBX_CELL_SIM_USIM_PHONEBOOK_READY, // Not reported by SARA-R5 + // UBX_CELL_SIM_TOOLKIT_REFRESH_SUCCESSFUL, // Not reported by SARA-R5 + // UBX_CELL_SIM_TOOLKIT_REFRESH_UNSUCCESSFUL, // Not reported by SARA-R5 + // UBX_CELL_SIM_PPP_CONNECTION_ACTIVE, // Not reported by SARA-R5 + // UBX_CELL_SIM_VOICE_CALL_ACTIVE, // Not reported by SARA-R5 + // UBX_CELL_SIM_CSD_CALL_ACTIVE // Not reported by SARA-R5 +} UBX_CELL_sim_states_t; + +#define UBX_CELL_NUM_PSD_PROFILES 6 // Number of supported PSD profiles +#define UBX_CELL_NUM_PDP_CONTEXT_IDENTIFIERS 11 // Number of supported PDP context identifiers +#define UBX_CELL_NUM_HTTP_PROFILES 4 // Number of supported HTTP profiles + +typedef enum +{ + UBX_CELL_HTTP_OP_CODE_SERVER_IP = 0, + UBX_CELL_HTTP_OP_CODE_SERVER_NAME, + UBX_CELL_HTTP_OP_CODE_USERNAME, + UBX_CELL_HTTP_OP_CODE_PASSWORD, + UBX_CELL_HTTP_OP_CODE_AUTHENTICATION, + UBX_CELL_HTTP_OP_CODE_SERVER_PORT, + UBX_CELL_HTTP_OP_CODE_SECURE, + UBX_CELL_HTTP_OP_CODE_REQUEST_TIMEOUT, + UBX_CELL_HTTP_OP_CODE_ADD_CUSTOM_HEADERS = 9 +} UBX_CELL_http_op_codes_t; + +typedef enum +{ + UBX_CELL_HTTP_COMMAND_HEAD = 0, + UBX_CELL_HTTP_COMMAND_GET, + UBX_CELL_HTTP_COMMAND_DELETE, + UBX_CELL_HTTP_COMMAND_PUT, + UBX_CELL_HTTP_COMMAND_POST_FILE, + UBX_CELL_HTTP_COMMAND_POST_DATA, + UBX_CELL_HTTP_COMMAND_GET_FOTA = 100 +} UBX_CELL_http_commands_t; + +typedef enum +{ + UBX_CELL_HTTP_CONTENT_APPLICATION_X_WWW = 0, + UBX_CELL_HTTP_CONTENT_TEXT_PLAIN, + UBX_CELL_HTTP_CONTENT_APPLICATION_OCTET, + UBX_CELL_HTTP_CONTENT_MULTIPART_FORM, + UBX_CELL_HTTP_CONTENT_APPLICATION_JSON, + UBX_CELL_HTTP_CONTENT_APPLICATION_XML, + UBX_CELL_HTTP_CONTENT_USER_DEFINED +} UBX_CELL_http_content_types_t; + +typedef enum +{ + UBX_CELL_MQTT_NV_RESTORE = 0, + UBX_CELL_MQTT_NV_SET, + UBX_CELL_MQTT_NV_STORE, +} UBX_CELL_mqtt_nv_parameter_t; + +typedef enum +{ + UBX_CELL_MQTT_PROFILE_CLIENT_ID = 0, + UBX_CELL_MQTT_PROFILE_SERVERNAME = 2, + UBX_CELL_MQTT_PROFILE_IPADDRESS, + UBX_CELL_MQTT_PROFILE_USERNAMEPWD, + UBX_CELL_MQTT_PROFILE_QOS = 6, + UBX_CELL_MQTT_PROFILE_RETAIN, + UBX_CELL_MQTT_PROFILE_TOPIC, + UBX_CELL_MQTT_PROFILE_MESSAGE, + UBX_CELL_MQTT_PROFILE_INACTIVITYTIMEOUT, + UBX_CELL_MQTT_PROFILE_SECURE, +} UBX_CELL_mqtt_profile_opcode_t; + +typedef enum +{ + UBX_CELL_MQTT_COMMAND_INVALID = -1, + UBX_CELL_MQTT_COMMAND_LOGOUT = 0, + UBX_CELL_MQTT_COMMAND_LOGIN, + UBX_CELL_MQTT_COMMAND_PUBLISH, + UBX_CELL_MQTT_COMMAND_PUBLISHFILE, + UBX_CELL_MQTT_COMMAND_SUBSCRIBE, + UBX_CELL_MQTT_COMMAND_UNSUBSCRIBE, + UBX_CELL_MQTT_COMMAND_READ, + UBX_CELL_MQTT_COMMAND_RCVMSGFORMAT, + UBX_CELL_MQTT_COMMAND_PING, + UBX_CELL_MQTT_COMMAND_PUBLISHBINARY, +} UBX_CELL_mqtt_command_opcode_t; + +constexpr uint16_t MAX_MQTT_HEX_MSG_LEN = 512; +constexpr uint16_t MAX_MQTT_DIRECT_MSG_LEN = 1024; + +typedef enum +{ + UBX_CELL_FTP_PROFILE_IPADDRESS = 0, + UBX_CELL_FTP_PROFILE_SERVERNAME, + UBX_CELL_FTP_PROFILE_USERNAME, + UBX_CELL_FTP_PROFILE_PWD, + UBX_CELL_FTP_PROFILE_ACCOUNT, + UBX_CELL_FTP_PROFILE_TIMEOUT, + UBX_CELL_FTP_PROFILE_MODE +} UBX_CELL_ftp_profile_opcode_t; + +typedef enum +{ + UBX_CELL_FTP_COMMAND_INVALID = -1, + UBX_CELL_FTP_COMMAND_LOGOUT = 0, + UBX_CELL_FTP_COMMAND_LOGIN, + UBX_CELL_FTP_COMMAND_DELETE_FILE, + UBX_CELL_FTP_COMMAND_RENAME_FILE, + UBX_CELL_FTP_COMMAND_GET_FILE, + UBX_CELL_FTP_COMMAND_PUT_FILE, + UBX_CELL_FTP_COMMAND_GET_FILE_DIRECT, + UBX_CELL_FTP_COMMAND_PUT_FILE_DIRECT, + UBX_CELL_FTP_COMMAND_CHANGE_DIR, + UBX_CELL_FTP_COMMAND_MKDIR = 10, + UBX_CELL_FTP_COMMAND_RMDIR, + UBX_CELL_FTP_COMMAND_DIR_INFO = 13, + UBX_CELL_FTP_COMMAND_LS, + UBX_CELL_FTP_COMMAND_GET_FOTA_FILE = 100 +} UBX_CELL_ftp_command_opcode_t; + +typedef enum +{ + UBX_CELL_PSD_CONFIG_PARAM_PROTOCOL = 0, + UBX_CELL_PSD_CONFIG_PARAM_APN, + // UBX_CELL_PSD_CONFIG_PARAM_USERNAME, // Not allowed on SARA-R5 + // UBX_CELL_PSD_CONFIG_PARAM_PASSWORD, // Not allowed on SARA-R5 + UBX_CELL_PSD_CONFIG_PARAM_DNS1 = 4, + UBX_CELL_PSD_CONFIG_PARAM_DNS2, + // UBX_CELL_PSD_CONFIG_PARAM_AUTHENTICATION, // Not allowed on SARA-R5 + // UBX_CELL_PSD_CONFIG_PARAM_IP_ADDRESS, // Not allowed on SARA-R5 + // UBX_CELL_PSD_CONFIG_PARAM_DATA_COMPRESSION, // Not allowed on SARA-R5 + // UBX_CELL_PSD_CONFIG_PARAM_HEADER_COMPRESSION, // Not allowed on SARA-R5 + UBX_CELL_PSD_CONFIG_PARAM_MAP_TO_CID = 100 +} UBX_CELL_pdp_configuration_parameter_t; + +typedef enum +{ + UBX_CELL_PSD_PROTOCOL_IPV4 = 0, + UBX_CELL_PSD_PROTOCOL_IPV6, + UBX_CELL_PSD_PROTOCOL_IPV4V6_V4_PREF, + UBX_CELL_PSD_PROTOCOL_IPV4V6_V6_PREF +} UBX_CELL_pdp_protocol_type_t; + +typedef enum +{ + UBX_CELL_PSD_ACTION_RESET = 0, + UBX_CELL_PSD_ACTION_STORE, + UBX_CELL_PSD_ACTION_LOAD, + UBX_CELL_PSD_ACTION_ACTIVATE, + UBX_CELL_PSD_ACTION_DEACTIVATE +} UBX_CELL_pdp_actions_t; + +typedef enum +{ + UBX_CELL_SEC_PROFILE_PARAM_CERT_VAL_LEVEL = 0, + UBX_CELL_SEC_PROFILE_PARAM_TLS_VER, + UBX_CELL_SEC_PROFILE_PARAM_CYPHER_SUITE, + UBX_CELL_SEC_PROFILE_PARAM_ROOT_CA, + UBX_CELL_SEC_PROFILE_PARAM_HOSTNAME, + UBX_CELL_SEC_PROFILE_PARAM_CLIENT_CERT, + UBX_CELL_SEC_PROFILE_PARAM_CLIENT_KEY, + UBX_CELL_SEC_PROFILE_PARAM_CLIENT_KEY_PWD, + UBX_CELL_SEC_PROFILE_PARAM_PSK, + UBX_CELL_SEC_PROFILE_PARAM_PSK_IDENT, + UBX_CELL_SEC_PROFILE_PARAM_SNI, +} UBX_CELL_sec_profile_parameter_t; + +typedef enum +{ + UBX_CELL_SEC_PROFILE_CERTVAL_OPCODE_NO = 0, + UBX_CELL_SEC_PROFILE_CERTVAL_OPCODE_YESNOURL, + UBX_CELL_SEC_PROFILE_CERVTAL_OPCODE_YESURL, + UBX_CELL_SEC_PROFILE_CERTVAL_OPCODE_YESURLDATE, +} UBX_CELL_sec_profile_certval_op_code_t; + +typedef enum +{ + UBX_CELL_SEC_PROFILE_TLS_OPCODE_ANYVER = 0, + UBX_CELL_SEC_PROFILE_TLS_OPCODE_VER1_0, + UBX_CELL_SEC_PROFILE_TLS_OPCODE_VER1_1, + UBX_CELL_SEC_PROFILE_TLS_OPCODE_VER1_2, + UBX_CELL_SEC_PROFILE_TLS_OPCODE_VER1_3, +} UBX_CELL_sec_profile_tls_op_code_t; + +typedef enum +{ + UBX_CELL_SEC_PROFILE_SUITE_OPCODE_PROPOSEDDEFAULT = 0, +} UBX_CELL_sec_profile_suite_op_code_t; + +typedef enum +{ + UBX_CELL_SEC_MANAGER_OPCODE_IMPORT = 0, +} UBX_CELL_sec_manager_opcode_t; + +typedef enum +{ + UBX_CELL_SEC_MANAGER_ROOTCA = 0, + UBX_CELL_SEC_MANAGER_CLIENT_CERT, + UBX_CELL_SEC_MANAGER_CLIENT_KEY, + UBX_CELL_SEC_MANAGER_SERVER_CERT +} UBX_CELL_sec_manager_parameter_t; + +typedef enum +{ + MINIMUM_FUNCTIONALITY = + 0, // (disable both transmit and receive RF circuits by deactivating both CS and PS services) + FULL_FUNCTIONALITY = 1, + AIRPLANE_MODE = 4, + SIM_TOOLKIT_ENABLE_DEDICATED = 6, + SIM_TOOLKIT_DISABLE_DEDICATED = 7, + SIM_TOOLKIT_ENABLE_RAW = 9, + FAST_SAFE_POWER_OFF = 10, + // SILENT_RESET_WITHOUT_SIM = 15, // Not supported on SARA-R5 + SILENT_RESET_WITH_SIM = 16 + // MINIMUM_FUNCTIONALITY = 19, // Not supported on SARA-R5 + // DEEP_LOW_POWER_STATE = 127 // Not supported on SARA-R5 +} UBX_CELL_functionality_t; + +class SparkFun_ublox_Cellular : public Print +{ + public: + // Constructor + // The library will use the powerPin and resetPin (if provided) to power the module off/on and perform an emergency + // reset maxInitTries sets the maximum number of initialization attempts. .init is called by .begin. + SparkFun_ublox_Cellular(int powerPin = UBX_CELL_POWER_PIN, int resetPin = UBX_CELL_RESET_PIN, uint8_t maxInitTries = 9); + + ~SparkFun_ublox_Cellular(); + // Begin -- initialize module and ensure it's connected +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + bool begin(SoftwareSerial &softSerial, unsigned long baud = UBX_CELL_DEFAULT_BAUD_RATE); +#endif + bool begin(HardwareSerial &hardSerial, unsigned long baud = UBX_CELL_DEFAULT_BAUD_RATE); + + // Debug prints + void enableDebugging( + Print &debugPort = Serial); // Turn on debug printing. If user doesn't specify then Serial will be used. + void enableAtDebugging( + Print &debugPort = Serial); // Turn on AT debug printing. If user doesn't specify then Serial will be used. + + // Invert the polarity of the power pin - if required + // Normally the SARA's power pin is pulled low and released to toggle the power + // But the Asset Tracker needs this to be pulled high and released instead + void invertPowerPin(bool invert = false); + + UBX_CELL_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 - 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 processes 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 Matthew! + 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)); // 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)(UBX_CELL_sim_states_t state)); + void setPSDActionCallback(void (*psdActionRequestCallback)(int result, IPAddress ip)); + void setPingCallback(void (*pingRequestCallback)(int retry, int p_size, String remote_hostname, IPAddress ip, + int ttl, long rtt)); + void setHTTPCommandCallback(void (*httpCommandRequestCallback)(int profile, int command, int result)); + void setMQTTCommandCallback(void (*mqttCommandRequestCallback)(int command, int result)); + void setFTPCommandCallback(void (*ftpCommandRequestCallback)(int command, int result)); + + UBX_CELL_error_t setRegistrationCallback(void (*registrationCallback)(UBX_CELL_registration_status_t status, + unsigned int lac, unsigned int ci, int Act)); + UBX_CELL_error_t setEpsRegistrationCallback(void (*epsRegistrationCallback)(UBX_CELL_registration_status_t status, + unsigned int tac, unsigned int ci, + int Act)); + + // Direct write/print to cell serial port + virtual size_t write(uint8_t c); + virtual size_t write(const char *str); + virtual size_t write(const char *buffer, size_t size); + + // General AT Commands + UBX_CELL_error_t at(void); + UBX_CELL_error_t enableEcho(bool enable = true); + String getManufacturerID(void); + String getModelID(void); + String getFirmwareVersion(void); + String getSerialNo(void); + String getIMEI(void); + String getIMSI(void); + String getCCID(void); + String getSubscriberNo(void); + String getCapabilities(void); + + // Control and status AT commands + UBX_CELL_error_t reset(void); + String clock(void); + // TODO: Return a clock struct + UBX_CELL_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 +/- and is in increments of 15 minutes. -28 == 7 hours behind UTC/GMT + UBX_CELL_error_t setClock(String theTime); + UBX_CELL_error_t setClock( + uint8_t y, uint8_t mo, uint8_t d, 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 + void autoTimeZoneForBegin(bool enable = true); // Call autoTimeZoneForBegin(false) _before_ .begin if you want to + // disable the automatic time zone + UBX_CELL_error_t autoTimeZone(bool enable); // Enable/disable automatic time zone adjustment + + // Network service AT commands + int8_t rssi(void); // Receive signal strength + UBX_CELL_error_t getExtSignalQuality(signal_quality &signal_quality); + + UBX_CELL_registration_status_t registration(bool eps = true); + bool setNetworkProfile(mobile_network_operator_t mno, bool autoReset = false, bool urcNotification = false); + mobile_network_operator_t getNetworkProfile(void); + typedef enum + { + PDP_TYPE_INVALID = -1, + PDP_TYPE_IP = 0, + PDP_TYPE_NONIP = 1, + PDP_TYPE_IPV4V6 = 2, + PDP_TYPE_IPV6 = 3 + } UBX_CELL_pdp_type; + UBX_CELL_error_t setAPN(String apn, uint8_t cid = 1, + UBX_CELL_pdp_type pdpType = PDP_TYPE_IP); // Set the Access Point Name + UBX_CELL_error_t getAPN( + int cid, String *apn, IPAddress *ip, + UBX_CELL_pdp_type *pdpType = nullptr); // Return the apn and IP address for the chosen context identifier + + UBX_CELL_error_t getSimStatus(String *code); + UBX_CELL_error_t setSimPin(String pin); + + // SIM + // Status report Mode: + // Bit States reported + // 0 Reports the (U)SIM initialization status ('s from 0 to 6 may be reported) + // 1 Reports the (U)SIM phonebook initialization status ('s from 7 to 8 may be reported) + // 2 Reports the (U)SIM toolkit REFRESH proactive command execution result ('s from 9 to 13 may be + // reported) Note: For the SARA-R5: =7, 8, 9, 10, 11, 12 and 13 are not reported. + UBX_CELL_error_t setSIMstateReportingMode(int mode); + UBX_CELL_error_t getSIMstateReportingMode(int *mode); + + typedef enum + { + L2P_DEFAULT, + L2P_PPP, + L2P_M_HEX, + L2P_M_RAW_IP, + L2P_M_OPT_PPP + } UBX_CELL_l2p_t; + UBX_CELL_error_t enterPPP(uint8_t cid = 1, char dialing_type_char = 0, unsigned long dialNumber = 99, + UBX_CELL_l2p_t l2p = L2P_DEFAULT); + + uint8_t getOperators(struct operator_stats *op, int maxOps = 3); + UBX_CELL_error_t registerOperator(struct operator_stats oper); + UBX_CELL_error_t automaticOperatorSelection(); + UBX_CELL_error_t getOperator(String *oper); + UBX_CELL_error_t deregisterOperator(void); + + // SMS -- Short Messages Service + UBX_CELL_error_t setSMSMessageFormat(UBX_CELL_message_format_t textMode = UBX_CELL_MESSAGE_FORMAT_TEXT); + UBX_CELL_error_t sendSMS(String number, String message); + UBX_CELL_error_t getPreferredMessageStorage(int *used, int *total, String memory = "ME"); + UBX_CELL_error_t readSMSmessage(int location, String *unread, String *from, String *dateTime, String *message); + UBX_CELL_error_t deleteSMSmessage( + int location, int deleteFlag = 0); // Default to deleting the single message at the specified location + UBX_CELL_error_t deleteReadSMSmessages(void) + { + return (deleteSMSmessage(1, 1)); + }; // Delete all the read messages from preferred storage + UBX_CELL_error_t deleteReadSentSMSmessages(void) + { + return (deleteSMSmessage(1, 2)); + }; // Delete the read and sent messages from preferred storage + UBX_CELL_error_t deleteReadSentUnsentSMSmessages(void) + { + return (deleteSMSmessage(1, 3)); + }; // Delete the read, sent and unsent messages from preferred storage + UBX_CELL_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 + UBX_CELL_error_t setBaud(unsigned long baud); + UBX_CELL_error_t setFlowControl(UBX_CELL_flow_control_t value = UBX_CELL_ENABLE_FLOW_CONTROL); + + // GPIO + // GPIO pin map + typedef enum + { + GPIO1 = 16, + GPIO2 = 23, + GPIO3 = 24, + GPIO4 = 25, + GPIO5 = 42, + GPIO6 = 19 + } UBX_CELL_gpio_t; + // GPIO pin modes + typedef enum + { + GPIO_MODE_INVALID = -1, + GPIO_OUTPUT = 0, + GPIO_INPUT, + NETWORK_STATUS, + GNSS_SUPPLY_ENABLE, + GNSS_DATA_READY, + GNSS_RTC_SHARING, + JAMMING_DETECTION, + SIM_CARD_DETECTION, + HEADSET_DETECTION, + GSM_TX_BURST_INDICATION, + MODULE_STATUS_INDICATION, + MODULE_OPERATING_MODE_INDICATION, + I2S_DIGITAL_AUDIO_INTERFACE, + SPI_SERIAL_INTERFACE, + MASTER_CLOCK_GENRATION, + UART_INTERFACE, + WIFI_ENABLE, + RING_INDICATION = 18, + LAST_GASP_ENABLE, + EXTERNAL_GNSS_ANTENNA, + TIME_PULSE_GNSS, + TIME_PULSE_OUTPUT, + TIMESTAMP, + FAST_POWER_OFF, + LWM2M_PULSE, + HARDWARE_FLOW_CONTROL, + ANTENNA_TUNING, + EXT_GNSS_TIME_PULSE, + EXT_GNSS_TIMESTAMP, + DTR_MODE, + KHZ_32768_OUT = 32, + PAD_DISABLED = 255 + } UBX_CELL_gpio_mode_t; + UBX_CELL_error_t setGpioMode(UBX_CELL_gpio_t gpio, UBX_CELL_gpio_mode_t mode, int value = 0); + UBX_CELL_gpio_mode_t getGpioMode(UBX_CELL_gpio_t gpio); + + // IP Transport Layer + int socketOpen(UBX_CELL_socket_protocol_t protocol, + unsigned int localPort = 0); // Open a socket. Returns the socket number. + UBX_CELL_error_t socketClose(int socket, unsigned long timeout = UBX_CELL_2_MIN_TIMEOUT); // Close the socket + UBX_CELL_error_t socketConnect(int socket, const char *address, + unsigned int port); // TCP - connect to a remote IP Address using the specified port. + // Not required for UDP sockets. + UBX_CELL_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 + UBX_CELL_error_t socketWrite(int socket, const char *str, int len = -1); + UBX_CELL_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 + UBX_CELL_error_t socketWriteUDP(int socket, const char *address, int port, const char *str, int len = -1); + UBX_CELL_error_t socketWriteUDP(int socket, IPAddress address, int port, const char *str, int len = -1); + UBX_CELL_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 bytesRead - if provided - will be updated with the number of bytes actually read. This could be less + // than length! + UBX_CELL_error_t socketRead(int socket, int length, char *readDest, int *bytesRead = nullptr); + // 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 + UBX_CELL_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 nullptr) + // bytesRead - if provided - will be updated with the number of bytes actually read. This could be less than length! + UBX_CELL_error_t socketReadUDP(int socket, int length, char *readDest, IPAddress *remoteIPAddress = nullptr, + int *remotePort = nullptr, int *bytesRead = nullptr); + // Return the number of bytes available (waiting to be read) on the chosen UDP socket + UBX_CELL_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 + UBX_CELL_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. + UBX_CELL_error_t socketDirectLinkMode(int socket); + // Configure when direct link data is sent + UBX_CELL_error_t socketDirectLinkTimeTrigger(int socket, unsigned long timerTrigger); + UBX_CELL_error_t socketDirectLinkDataLengthTrigger(int socket, int dataLengthTrigger); + UBX_CELL_error_t socketDirectLinkCharacterTrigger(int socket, int characterTrigger); + UBX_CELL_error_t socketDirectLinkCongestionTimer(int socket, unsigned long congestionTimer); + // Use +USOCTL (Socket control) to query the socket parameters + UBX_CELL_error_t querySocketType(int socket, UBX_CELL_socket_protocol_t *protocol); + UBX_CELL_error_t querySocketLastError(int socket, int *error); + UBX_CELL_error_t querySocketTotalBytesSent(int socket, uint32_t *total); + UBX_CELL_error_t querySocketTotalBytesReceived(int socket, uint32_t *total); + UBX_CELL_error_t querySocketRemoteIPAddress(int socket, IPAddress *address, int *port); + UBX_CELL_error_t querySocketStatusTCP(int socket, UBX_CELL_tcp_socket_status_t *status); + UBX_CELL_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 + UBX_CELL_error_t ping(String remote_host, int retry = 4, int p_size = 32, unsigned long timeout = 5000, + int ttl = 32); + + // HTTP + UBX_CELL_error_t resetHTTPprofile(int profile); // Reset the HTTP profile. Note: The configured HTTP profile + // parameters are not saved in the non volatile memory. + UBX_CELL_error_t setHTTPserverIPaddress(int profile, IPAddress address); // Default: empty string + UBX_CELL_error_t setHTTPserverName(int profile, String server); // Default: empty string + UBX_CELL_error_t setHTTPusername(int profile, String username); // Default: empty string + UBX_CELL_error_t setHTTPpassword(int profile, String password); // Default: empty string + UBX_CELL_error_t setHTTPauthentication(int profile, bool authenticate); // Default: no authentication + UBX_CELL_error_t setHTTPserverPort(int profile, int port); // Default: 80 + UBX_CELL_error_t setHTTPcustomHeader(int profile, + String header); // Default: format 0:Content-Type:application/json" + UBX_CELL_error_t setHTTPsecure( + int profile, bool secure, + int secprofile = -1); // Default: disabled (HTTP on port 80). Set to true for HTTPS on port 443 + // TO DO: Add custom request headers + UBX_CELL_error_t getHTTPprotocolError(int profile, int *error_class, + int *error_code); // Read the most recent HTTP protocol error for this profile + UBX_CELL_error_t sendHTTPGET(int profile, String path, String responseFilename); + UBX_CELL_error_t sendHTTPPOSTdata(int profile, String path, String responseFilename, String data, + UBX_CELL_http_content_types_t httpContentType); + UBX_CELL_error_t sendHTTPPOSTfile(int profile, String path, String responseFilename, String requestFile, + UBX_CELL_http_content_types_t httpContentType); + + UBX_CELL_error_t nvMQTT(UBX_CELL_mqtt_nv_parameter_t parameter); + UBX_CELL_error_t setMQTTclientId(const String &clientId); + UBX_CELL_error_t setMQTTserver(const String &serverName, int port); + UBX_CELL_error_t setMQTTcredentials(const String &userName, const String &pwd); + UBX_CELL_error_t setMQTTsecure(bool secure, int secprofile = -1); + UBX_CELL_error_t connectMQTT(void); + UBX_CELL_error_t disconnectMQTT(void); + UBX_CELL_error_t subscribeMQTTtopic(int max_Qos, const String &topic); + UBX_CELL_error_t unsubscribeMQTTtopic(const String &topic); + UBX_CELL_error_t readMQTT(int *pQos, String *pTopic, uint8_t *readDest, int readLength, int *bytesRead); + UBX_CELL_error_t mqttPublishTextMsg(const String &topic, const char *const msg, uint8_t qos = 0, + bool retain = false); + UBX_CELL_error_t mqttPublishBinaryMsg(const String &topic, const char *const msg, size_t msg_len, uint8_t qos = 0, + bool retain = false); + UBX_CELL_error_t mqttPublishFromFile(const String &topic, const String &filename, uint8_t qos = 0, + bool retain = false); + UBX_CELL_error_t getMQTTprotocolError(int *error_code, int *error_code2); + + // FTP + UBX_CELL_error_t setFTPserver(const String &serverName); + UBX_CELL_error_t setFTPtimeouts(const unsigned int timeout, const unsigned int cmd_linger, + const unsigned int data_linger); + UBX_CELL_error_t setFTPcredentials(const String &userName, const String &pwd); + UBX_CELL_error_t connectFTP(void); + UBX_CELL_error_t disconnectFTP(void); + UBX_CELL_error_t ftpGetFile(const String &filename); + UBX_CELL_error_t getFTPprotocolError(int *error_code, int *error_code2); + + // Configure security profiles + UBX_CELL_error_t resetSecurityProfile(int secprofile); + UBX_CELL_error_t configSecurityProfileString(int secprofile, UBX_CELL_sec_profile_parameter_t parameter, + String value); + UBX_CELL_error_t configSecurityProfile(int secprofile, UBX_CELL_sec_profile_parameter_t parameter, int value); + UBX_CELL_error_t setSecurityManager(UBX_CELL_sec_manager_opcode_t opcode, + UBX_CELL_sec_manager_parameter_t parameter, String name, String data); + + UBX_CELL_error_t activatePDPcontext( + bool status, int cid = -1); // Activates or deactivates the specified PDP context. Default to all (cid = -1) + + // GPS + typedef enum + { + GNSS_SYSTEM_GPS = 1, + GNSS_SYSTEM_SBAS = 2, + GNSS_SYSTEM_GALILEO = 4, + GNSS_SYSTEM_BEIDOU = 8, + GNSS_SYSTEM_IMES = 16, + GNSS_SYSTEM_QZSS = 32, + GNSS_SYSTEM_GLONASS = 64 + } gnss_system_t; + typedef enum + { + GNSS_AIDING_MODE_NONE = 0, + GNSS_AIDING_MODE_AUTOMATIC = 1, + GNSS_AIDING_MODE_ASSISTNOW_OFFLINE = 2, + GNSS_AIDING_MODE_ASSISTNOW_ONLINE = 4, + GNSS_AIDING_MODE_ASSISTNOW_AUTONOMOUS = 8 + } gnss_aiding_mode_t; + bool isGPSon(void); + UBX_CELL_error_t gpsPower(bool enable = true, gnss_system_t gnss_sys = GNSS_SYSTEM_GPS, + gnss_aiding_mode_t gnss_aiding = GNSS_AIDING_MODE_AUTOMATIC); + // UBX_CELL_error_t gpsEnableClock(bool enable = true); + // UBX_CELL_error_t gpsGetClock(struct ClockData *clock); + // UBX_CELL_error_t gpsEnableFix(bool enable = true); + // UBX_CELL_error_t gpsGetFix(float *lat, float *lon, unsigned int *alt, uint8_t *quality, uint8_t *sat); + // UBX_CELL_error_t gpsGetFix(struct PositionData *pos); + // UBX_CELL_error_t gpsEnablePos(bool enable = true); + // UBX_CELL_error_t gpsGetPos(struct PositionData *pos); + // UBX_CELL_error_t gpsEnableSat(bool enable = true); + // UBX_CELL_error_t gpsGetSat(uint8_t *sats); + UBX_CELL_error_t gpsEnableRmc(bool enable = true); // Enable GPRMC messages + UBX_CELL_error_t gpsGetRmc(struct PositionData *pos, struct SpeedData *speed, struct ClockData *clk, + bool *valid); // Parse a GPRMC message + // UBX_CELL_error_t gpsEnableSpeed(bool enable = true); + // UBX_CELL_error_t gpsGetSpeed(struct SpeedData *speed); + + UBX_CELL_error_t gpsRequest(unsigned int timeout, uint32_t accuracy, bool detailed = true, unsigned int sensor = 3); + + // CellLocate + UBX_CELL_error_t gpsAidingServerConf(const char *primaryServer, const char *secondaryServer, const char *authToken, + unsigned int days = 14, unsigned int period = 4, unsigned int resolution = 1, + unsigned int gnssTypes = 65, unsigned int mode = 0, + unsigned int dataType = 15); + + // File system + // TO DO: add full support for file tags. Default tag to USER + UBX_CELL_error_t getFileContents( + String filename, + String *contents); // OK for text files. But will fail with binary files (containing \0) on some platforms. + UBX_CELL_error_t getFileContents(String filename, + char *contents); // OK for binary files. Make sure contents can hold the entire + // file. Get the size first with getFileSize. + UBX_CELL_error_t getFileBlock( + const String &filename, char *buffer, size_t offset, size_t length, + size_t &bytes_read); // OK for binary files. Make sure buffer can hold the requested block size. + + // Append data to a file, delete file first to not appends the data. + UBX_CELL_error_t appendFileContents(String filename, String str); + UBX_CELL_error_t appendFileContents(String filename, const char *str, int len); + UBX_CELL_error_t getFileSize(String filename, int *size); + UBX_CELL_error_t deleteFile(String filename); + + // Functionality + UBX_CELL_error_t functionality(UBX_CELL_functionality_t function = FULL_FUNCTIONALITY); + + // Send a custom command with an expected (potentially partial) response, store entire response + UBX_CELL_error_t sendCustomCommandWithResponse(const char *command, const char *expectedResponse, + char *responseDest, + unsigned long commandTimeout = UBX_CELL_STANDARD_RESPONSE_TIMEOUT, + bool at = true); + + // Send command with an expected (potentially partial) response, store entire response + UBX_CELL_error_t sendCommandWithResponse(const char *command, const char *expectedResponse, char *responseDest, + unsigned long commandTimeout, int destSize = minimumResponseAllocation, + bool at = true); + + char *ubx_cell_calloc_char(size_t num); + + // Add a URC handler + void addURCHandler(const char *urcString, UBX_CELL_urc_handler_t urcHandler); + + protected: + HardwareSerial *_hardSerial; +#ifdef UBX_CELL_SOFTWARE_SERIAL_ENABLED + SoftwareSerial *_softSerial; +#endif + + Print *_debugPort; // The stream to send debug messages to if enabled. Usually Serial. + bool _printDebug = false; // Flag to print debugging variables + Print *_debugAtPort; // The stream to send debug messages to if enabled. Usually Serial. + bool _printAtDebug = false; // Flag to print debugging variables + + int _powerPin; + int _resetPin; + bool _invertPowerPin = false; + + unsigned long _baud; + IPAddress _lastRemoteIP; + IPAddress _lastLocalIP; + uint8_t _maxInitTries; + bool _autoTimeZoneForBegin = true; + bool _bufferedPollReentrant = + false; // Prevent reentry of bufferedPoll - just in case it gets called from a callback + bool _pollReentrant = false; // Prevent reentry of poll - just in case it gets called from a callback + +#define _RXBuffSize 2056 + const unsigned long _rxWindowMillis = 2; // 1ms is not quite long enough for a single char at 9600 baud. millis roll + // over much less often than micros. See notes in .cpp re. ESP32! + char *_saraRXBuffer; // Allocated in UBX_CELL::begin + char *_pruneBuffer; + char *_saraResponseBacklog; + int _saraResponseBacklogLength = + 0; // The backlog could contain binary data so we can't use strlen to find its length + + void (*_socketListenCallback)(int, IPAddress, unsigned int, int, IPAddress, unsigned int); + void (*_socketReadCallback)(int, String); + void (*_socketReadCallbackPlus)(int, const char *, int, IPAddress, + int); // socket, data, length, remoteAddress, remotePort + void (*_socketCloseCallback)(int); + void (*_gpsRequestCallback)(ClockData, PositionData, SpeedData, unsigned long); + void (*_simStateReportCallback)(UBX_CELL_sim_states_t); + void (*_psdActionRequestCallback)(int, IPAddress); + void (*_pingRequestCallback)(int, int, String, IPAddress, int, long); + void (*_httpCommandRequestCallback)(int, int, int); + void (*_mqttCommandRequestCallback)(int, int); + void (*_ftpCommandRequestCallback)(int, int); + void (*_registrationCallback)(UBX_CELL_registration_status_t status, unsigned int lac, unsigned int ci, int Act); + void (*_epsRegistrationCallback)(UBX_CELL_registration_status_t status, unsigned int tac, unsigned int ci, int Act); + + // Vectors of URC strings and handlers + std::vector _urcStrings; + std::vector _urcHandlers; + + int _lastSocketProtocol[UBX_CELL_NUM_SOCKETS]; // Record the protocol for each socket to avoid having to call + // querySocketType in parseSocketReadIndication + + typedef enum + { + UBX_CELL_INIT_STANDARD, + UBX_CELL_INIT_AUTOBAUD, + UBX_CELL_INIT_RESET + } UBX_CELL_init_type_t; + + UBX_CELL_error_t init(unsigned long baud, UBX_CELL_init_type_t initType = UBX_CELL_INIT_STANDARD); + + void powerOn(void); // Brief pulse on PWR_ON to turn module back on + void powerOff(void); // Long pulse on PWR_ON to do a graceful shutdown. Note modulePowerOff (+CPWROFF) is preferred. + + void hwReset(void); + + UBX_CELL_error_t setMNOprofile(mobile_network_operator_t mno, bool autoReset = false, bool urcNotification = false); + UBX_CELL_error_t getMNOprofile(mobile_network_operator_t *mno); + + // Wait for an expected response (don't send a command) + UBX_CELL_error_t waitForResponse(const char *expectedResponse, const char *expectedError, uint16_t timeout); + + // Send a command -- prepend AT if at is true + void sendCommand(const char *command, bool at); + + const int _saraR5maxSocketRead = 1024; // The limit on bytes that can be read in a single read + + UBX_CELL_error_t parseSocketReadIndication(int socket, int length); + UBX_CELL_error_t parseSocketReadIndicationUDP(int socket, int length); + UBX_CELL_error_t parseSocketListenIndication(int listeningSocket, IPAddress localIP, unsigned int listeningPort, + int socket, IPAddress remoteIP, unsigned int port); + UBX_CELL_error_t parseSocketCloseIndication(String *closeIndication); + + // UART Functions + size_t hwPrint(const char *s); + size_t hwWriteData(const char *buff, int len); + size_t hwWrite(const char c); + int readAvailable(char *inString); + char readChar(void); + int hwAvailable(void); + virtual void beginSerial(unsigned long baud); + void setTimeout(unsigned long timeout); + bool find(char *target); + + UBX_CELL_error_t autobaud(unsigned long desiredBaud); + + bool urcHandlerReadSocket(const char *event); + bool urcHandlerReadUDPSocket(const char *event); + bool urcHandlerListeningSocket(const char *event); + bool urcHandlerCloseSocket(const char *event); + bool urcHandlerGNSSRequestLocation(const char *event); + bool urcHandlerSIMState(const char *event); + bool urcHandlerHTTPCommand(const char *event); + bool urcHandlerMQTTCommand(const char *event); + bool urcHandlerPingCommand(const char *event); + bool urcHandlerFTPCommand(const char *event); + bool urcHandlerRegistrationStatus(const char *event); + bool urcHandlerEPSRegistrationStatus(const char *event); + + bool processURCEvent(const char *event); + void pruneBacklog(void); + + // GPS Helper functions + char *readDataUntil(char *destination, unsigned int destSize, char *source, char delimiter); + bool parseGPRMCString(char *rmcString, PositionData *pos, ClockData *clk, SpeedData *spd); +}; + +#endif // SPARKFUN_UBX_CELL_ARDUINO_LIBRARY_H diff --git a/src/sfe_ublox_cellular_voice.h b/src/sfe_ublox_cellular_voice.h new file mode 100644 index 0000000..c824d02 --- /dev/null +++ b/src/sfe_ublox_cellular_voice.h @@ -0,0 +1,166 @@ +#ifndef SPARKFUN_UBX_CELL_VOICE_ARDUINO_LIBRARY_H +#define SPARKFUN_UBX_CELL_VOICE_ARDUINO_LIBRARY_H + +#include "sfe_ublox_cellular.h" + +const char *const UBX_CELL_COMMAND_DIAL = "D"; // Dial command +const char *const UBX_CELL_COMMAND_ANSWER = "A"; // Answer call +const char *const UBX_CELL_COMMAND_HANG_UP = "+CHUP"; // Hang up call +const char *const UBX_CELL_COMMAND_PLAY_AUDIO = "+UPAR"; // Play audio resource +const char *const UBX_CELL_COMMAND_STOP_AUDIO = "+USAR"; // Stop audio resource +const char *const UBX_CELL_COMMAND_GENERATE_TONE = "+UTGN"; // Tone generator + +const char *const UBX_CELL_RING_URC = "RING"; + +typedef enum +{ + UBX_CELL_AUDIO_RESOURCE_TONE = 0, + UBX_CELL_AUDIO_RESOURCE_MIDI = 1, + UBX_CELL_AUDIO_RESOURCE_LOOPBACK = 2 +} UBX_CELL_audio_resource_t; + +// Base class for any modules supporting voice calls +template class SparkFun_ublox_Cellular_Voice_Base +{ + public: + SparkFun_ublox_Cellular_Voice_Base(void) + { + // Set ring URC callback to nullptr + _ringCallback = nullptr; + + // Add handler for ring URC + static_cast(this)->addURCHandler(UBX_CELL_RING_URC, + [this](const char *event) { return this->urcCheckRing(event); }); + } + + UBX_CELL_error_t dial(String number) + { + char *command; + char *numberCStr; + UBX_CELL_error_t err; + + numberCStr = static_cast(this)->ubx_cell_calloc_char(number.length() + 1); + if (numberCStr == nullptr) + return UBX_CELL_ERROR_OUT_OF_MEMORY; + number.toCharArray(numberCStr, number.length() + 1); + + size_t cmdLen = strlen(UBX_CELL_COMMAND_DIAL) + strlen(numberCStr) + 3; + command = static_cast(this)->ubx_cell_calloc_char(cmdLen); + if (command != nullptr) + { + // Heads up! The dial command is one of the only commands that requires a + // semicolon at the end of it! + snprintf(command, cmdLen, "%s=%s;", UBX_CELL_COMMAND_DIAL, numberCStr); + + err = static_cast(this)->sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK, nullptr, + UBX_CELL_10_SEC_TIMEOUT); + + free(command); + } + else + { + err = UBX_CELL_ERROR_OUT_OF_MEMORY; + } + + free(numberCStr); + + return err; + } + + UBX_CELL_error_t answer(void) + { + return static_cast(this)->sendCommandWithResponse(UBX_CELL_COMMAND_ANSWER, UBX_CELL_RESPONSE_OK_OR_ERROR, + nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + } + + UBX_CELL_error_t hangUp(void) + { + return static_cast(this)->sendCommandWithResponse(UBX_CELL_COMMAND_HANG_UP, UBX_CELL_RESPONSE_OK_OR_ERROR, + nullptr, UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + } + + UBX_CELL_error_t playAudioResource(uint8_t audio_resource, uint8_t tone_id = 0, uint8_t nof_repeat = 0) + { + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_PLAY_AUDIO) + 13; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d,%d,%d", UBX_CELL_COMMAND_PLAY_AUDIO, audio_resource, tone_id, nof_repeat); + + err = static_cast(this)->sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; + } + + UBX_CELL_error_t stopAudioResource(uint8_t audio_resource) + { + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_STOP_AUDIO) + 5; + char command[cmdLen]; + + snprintf(command, cmdLen, "%s=%d", UBX_CELL_COMMAND_STOP_AUDIO, audio_resource); + + err = static_cast(this)->sendCommandWithResponse(command, UBX_CELL_RESPONSE_OK_OR_ERROR, nullptr, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; + } + + UBX_CELL_error_t generateToneFreq(uint16_t frequency, uint16_t duration, uint8_t volume) + { + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_GENERATE_TONE) + 15; + char command[cmdLen]; + char response[] = "\r\nOK\r\n\r\n+UUTGN: 0\r\n"; + + snprintf(command, cmdLen, "%s=%d,%d,%d", UBX_CELL_COMMAND_GENERATE_TONE, frequency, duration, volume); + + err = static_cast(this)->sendCommandWithResponse(command, response, nullptr, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; + } + + UBX_CELL_error_t generateToneDTMF(char dtmf_character, uint16_t duration, uint8_t volume) + { + UBX_CELL_error_t err; + size_t cmdLen = strlen(UBX_CELL_COMMAND_GENERATE_TONE) + 14; + char command[cmdLen]; + char response[] = "\r\nOK\r\n\r\n+UUTGN: 0\r\n"; + + snprintf(command, cmdLen, "%s=\"%c\",%d,%d", UBX_CELL_COMMAND_GENERATE_TONE, dtmf_character, duration, volume); + + err = static_cast(this)->sendCommandWithResponse(command, response, nullptr, + UBX_CELL_STANDARD_RESPONSE_TIMEOUT); + return err; + } + + void setRingCallback(void (*callback)(void)) + { + _ringCallback = callback; + } + + protected: + // Callback for incoming calls + void (*_ringCallback)(void); + + bool urcCheckRing(const char *event) + { + int socket, length; + char *searchPtr = strnstr(event, UBX_CELL_RING_URC, _RXBuffSize); + if (searchPtr != nullptr) + { + if (_ringCallback != nullptr) + { + _ringCallback(); + } + return true; + } + + return false; + } +}; + +class SparkFun_ublox_Cellular_Voice : public SparkFun_ublox_Cellular, public SparkFun_ublox_Cellular_Voice_Base +{ +}; + +#endif \ No newline at end of file