diff --git a/docs/api.md b/docs/api.md index 7d5b0e4..c423e4e 100644 --- a/docs/api.md +++ b/docs/api.md @@ -3,6 +3,7 @@ Members | Descriptions --------------------------------|--------------------------------------------- `class ` [`ArduinoCellular`](#class_arduino_cellular) | This class provides methods to interact with the Arduino Pro Modem, such as connecting to the network, sending [SMS](#class_s_m_s) messages, getting GPS location, and more. +`class ` [`ManagedTinyGsmClient`](#class_managed_tiny_gsm_client) | A managed client for TinyGSM that automatically handles socket allocation and release. This class allows you to create multiple clients without worrying about socket management. It uses a static bit field to track used sockets and provides methods to lock and unlock the sockets. `class ` [`ModemInterface`](#class_modem_interface) | Represents the interface to the 4G modem module which extends the TinyGsmBG96 class. `class ` [`SMS`](#class_s_m_s) | Represents an [SMS](#class_s_m_s) message. `class ` [`Time`](#class_time) | Represents a point in time with year, month, day, hour, minute, second, and offset. @@ -17,9 +18,15 @@ This class provides methods to interact with the Arduino Pro Modem, such as conn Members | Descriptions --------------------------------|--------------------------------------------- | [`ArduinoCellular`](#class_arduino_cellular_1a96d1d9f3fbe80adc3d460b4364d47870) | Creates an instance of the [ArduinoCellular](#class_arduino_cellular) class. | +| [`~ArduinoCellular`](#class_arduino_cellular_1af6b68a2c8c80d8667957b7eee7de1128) | Destructor for the [ArduinoCellular](#class_arduino_cellular) class. Cleans up any resources used by the class. | +| [`ArduinoCellular`](#class_arduino_cellular_1a7ac09e59bf694dd36c7dc50c748e33a0) | Deleted copy constructor and assignment operator to prevent copying. unique_ptr is used to manage the lifetime of the clients but they cannot be copied. | +| [`operator=`](#class_arduino_cellular_1ad30357ba5fa2222644995eff91447fe3) | Deleted assignment operator to prevent copying. unique_ptr is used to manage the lifetime of the clients but they cannot be copied. | +| [`ArduinoCellular`](#class_arduino_cellular_1a897e89d6dfd175fcc1fb41bf92089e08) | Move constructor for the [ArduinoCellular](#class_arduino_cellular) class. Allows moving the instance to another instance. | +| [`operator=`](#class_arduino_cellular_1aab11902057a738b4a2193fc0caadab0c) | Move assignment operator for the [ArduinoCellular](#class_arduino_cellular) class. Allows moving the instance to another instance. | | [`begin`](#class_arduino_cellular_1ad5ca7cf61f48c40569f41f3029d6516e) | Initializes the modem. This function must be called before using any other functions in the library. | | [`unlockSIM`](#class_arduino_cellular_1aa0be2795ff7b23c39ecef90d9906bbdf) | Unlocks the SIM card using the specified PIN. | -| [`connect`](#class_arduino_cellular_1a7fb3c3e841b39c4faacef32cec6277b4) | Registers with the cellular network and connects to the Internet if the APN, GPRS username, and GPRS password are provided. | +| [`connect`](#class_arduino_cellular_1ad50b506df65a6ac4c81b9e88095ecd24) | Registers with the cellular network and connects to the Internet if the APN, GPRS username, and GPRS password are provided. | +| [`connect`](#class_arduino_cellular_1a247ff63f5852b42f32103d4cef84b2a3) | Registers with the cellular network and connects to the Internet if the APN, GPRS username, and GPRS password are provided. | | [`isConnectedToOperator`](#class_arduino_cellular_1af7453ef90702e9042e2b4b18fa89db03) | Checks if the modem is registered on the network. | | [`isConnectedToInternet`](#class_arduino_cellular_1a6f8251e06de1810897b8bd8f8fb1b1a2) | Checks if the GPRS network is connected. | | [`enableGPS`](#class_arduino_cellular_1abe77a53e0eba6e8d62ba5db3bb6f5e92) | Enables or disables the GPS functionality. | @@ -27,18 +34,21 @@ This class provides methods to interact with the Arduino Pro Modem, such as conn | [`getCellularTime`](#class_arduino_cellular_1a6b3ce5485badff582584d539e790aff4) | Gets the current time from the network. | | [`getGPSTime`](#class_arduino_cellular_1a4aeb898c958e6eb001d606f0c7da8799) | Gets the current time from the GPS module. | | [`sendSMS`](#class_arduino_cellular_1a371aef1318857f0863f443eaeabf4ac2) | Sends an [SMS](#class_s_m_s) message to the specified number. | -| [`getReadSMS`](#class_arduino_cellular_1ae032c4e4cade6579a2c1edfe53d2ff2b) | Gets the list of read [SMS](#class_s_m_s) messages. | -| [`getUnreadSMS`](#class_arduino_cellular_1a212513654884058947a2a4d332f6ccfc) | Gets the list of unread [SMS](#class_s_m_s) messages. | +| [`getReadSMS`](#class_arduino_cellular_1a5da65683df86af75590c7a68766236ee) | Gets the list of read [SMS](#class_s_m_s) messages. | +| [`getUnreadSMS`](#class_arduino_cellular_1af1e3b2fad0a64f3b7675c88100ddbca5) | Gets the list of unread [SMS](#class_s_m_s) messages. | | [`deleteSMS`](#class_arduino_cellular_1abe4337f0bc8c486a076011309120ace1) | Deletes an [SMS](#class_s_m_s) message at the specified index. | | [`sendATCommand`](#class_arduino_cellular_1a58a3e3713af0c01ad1075a2509c6874d) | Sends an AT command to the modem and waits for a response, then returns the response. | | [`sendUSSDCommand`](#class_arduino_cellular_1a6886aec5850836ea8e8f135d4e5632ab) | Sends a USSD command to the network operator and waits for a response. | -| [`getNetworkClient`](#class_arduino_cellular_1acff92474af3bd819b62f132cf12f45ba) | Gets the Network client. (OSI Layer 3) | +| [`getNetworkClient`](#class_arduino_cellular_1acff92474af3bd819b62f132cf12f45ba) | Gets a new Network client. (OSI Layer 3) The library automatically manages the sockets, so you can create multiple clients without worrying about socket management. You should ensure that you release the client when you are done with it. It's possible that the client is invalid if no sockets are available. This is indicated by the isValid() method and the socketId will be -1. | | [`getSecureNetworkClient`](#class_arduino_cellular_1a8b7486d1a682787588c015af8d65a38e) | Gets the Transport Layer Security (TLS) client. (OSI Layer 4) | -| [`getHTTPClient`](#class_arduino_cellular_1aa1b4c3bbd14984d2a7ed1db7fa1ac930) | Gets the HTTP client for the specified server and port. | -| [`getHTTPSClient`](#class_arduino_cellular_1aeb2d1bff0405e92197c0de750cef87e0) | Gets the HTTPS client for the specified server and port. | +| [`getHTTPClient`](#class_arduino_cellular_1aa1b4c3bbd14984d2a7ed1db7fa1ac930) | Gets a HTTP client for the specified server and port. The maximum number of HTTP clients is limited by the number of sockets available. Call `[cleanup()](#class_arduino_cellular_1a13c1f1eecc24756cef564a2555cb829e)` to release the resources used by the clients once you are done with them. | +| [`getHTTPSClient`](#class_arduino_cellular_1aeb2d1bff0405e92197c0de750cef87e0) | Gets a HTTPS client for the specified server and port. The maximum number of HTTP clients is limited by the number of sockets available. Call `[cleanup()](#class_arduino_cellular_1a13c1f1eecc24756cef564a2555cb829e)` to release the resources used by the clients once you are done with them. | +| [`cleanup`](#class_arduino_cellular_1a13c1f1eecc24756cef564a2555cb829e) | Cleans up the clients and releases the resources used by them. It's necessary to call this function to free up the memory used by the client objects that are created by the library internally. | +| [`getManagedClientCount`](#class_arduino_cellular_1a4a845d55293e310f5f19bcc14ec135f3) | Gets the number of managed clients. The clients are managed in the sense of memory management. | | [`getIPAddress`](#class_arduino_cellular_1aabf2ad2144827d34c3ba298b5f423344) | Gets the local IP address. | | [`getSignalQuality`](#class_arduino_cellular_1aefdae9cb2b8c9f05130b09c18c3f245e) | Gets the signal quality. | | [`setDebugStream`](#class_arduino_cellular_1aae2cacf5a5778293f0bd3312d2289327) | Sets the debug stream for [ArduinoCellular](#class_arduino_cellular). | +| [`getSimStatus`](#class_arduino_cellular_1a25aae9d375b5a0e1c4271f06815adc49) | Gets the SIM card status. | ## Members @@ -52,6 +62,58 @@ Creates an instance of the [ArduinoCellular](#class_arduino_cellular) class.
+### `~ArduinoCellular` + +```cpp +~ArduinoCellular() +``` + +Destructor for the [ArduinoCellular](#class_arduino_cellular) class. Cleans up any resources used by the class. + +
+ +### `ArduinoCellular` + +```cpp +ArduinoCellular(const ArduinoCellular &) = delete +``` + +Deleted copy constructor and assignment operator to prevent copying. unique_ptr is used to manage the lifetime of the clients but they cannot be copied. + +
+ +### `operator=` + +```cpp +ArduinoCellular & operator=(const ArduinoCellular &) = delete +``` + +Deleted assignment operator to prevent copying. unique_ptr is used to manage the lifetime of the clients but they cannot be copied. + +
+ +### `ArduinoCellular` + +```cpp +ArduinoCellular( ArduinoCellular &&) = default +``` + +Move constructor for the [ArduinoCellular](#class_arduino_cellular) class. Allows moving the instance to another instance. + +
+ +### `operator=` + +```cpp +ArduinoCellular & operator=( ArduinoCellular &&) = default +``` + +Move assignment operator for the [ArduinoCellular](#class_arduino_cellular) class. Allows moving the instance to another instance. + +#### Returns +A reference to the moved instance. +
+ ### `begin` ```cpp @@ -77,10 +139,10 @@ Unlocks the SIM card using the specified PIN. True if the SIM card is unlocked, false otherwise.
-### `connect` +### `connect` ```cpp -bool connect(String apn, String username, String password) +bool connect(String apn, String username, String password, bool waitForever) ``` Registers with the cellular network and connects to the Internet if the APN, GPRS username, and GPRS password are provided. @@ -92,6 +154,25 @@ Registers with the cellular network and connects to the Internet if the APN, GPR * `password` The APN password. +* `waitForever` The function does not return unless a connection has been established + +#### Returns +True if the connection is successful, false otherwise. +
+ +### `connect` + +```cpp +bool connect(String apn, bool waitForever) +``` + +Registers with the cellular network and connects to the Internet if the APN, GPRS username, and GPRS password are provided. + +#### Parameters +* `apn` The Access Point Name. + +* `waitForever` The function does not return unless a connection has been established + #### Returns True if the connection is successful, false otherwise.
@@ -188,7 +269,7 @@ Sends an [SMS](#class_s_m_s) message to the specified number. * `message` The message to send.
-### `getReadSMS` +### `getReadSMS` ```cpp std::vector< SMS > getReadSMS() @@ -200,7 +281,7 @@ Gets the list of read [SMS](#class_s_m_s) messages. A vector of [SMS](#class_s_m_s) messages.
-### `getUnreadSMS` +### `getUnreadSMS` ```cpp std::vector< SMS > getUnreadSMS() @@ -265,10 +346,10 @@ The response from the network operator. (Note: The response may be an [SMS](#cla TinyGsmClient getNetworkClient() ``` -Gets the Network client. (OSI Layer 3) +Gets a new Network client. (OSI Layer 3) The library automatically manages the sockets, so you can create multiple clients without worrying about socket management. You should ensure that you release the client when you are done with it. It's possible that the client is invalid if no sockets are available. This is indicated by the isValid() method and the socketId will be -1. #### Returns -The GSM client. +A GSM client object that can be used to connect to a server.
### `getSecureNetworkClient` @@ -289,7 +370,7 @@ The GSM client. HttpClient getHTTPClient(const char * server, const int port) ``` -Gets the HTTP client for the specified server and port. +Gets a HTTP client for the specified server and port. The maximum number of HTTP clients is limited by the number of sockets available. Call `[cleanup()](#class_arduino_cellular_1a13c1f1eecc24756cef564a2555cb829e)` to release the resources used by the clients once you are done with them. #### Parameters * `server` The server address. @@ -306,7 +387,7 @@ The HTTP client. HttpClient getHTTPSClient(const char * server, const int port) ``` -Gets the HTTPS client for the specified server and port. +Gets a HTTPS client for the specified server and port. The maximum number of HTTP clients is limited by the number of sockets available. Call `[cleanup()](#class_arduino_cellular_1a13c1f1eecc24756cef564a2555cb829e)` to release the resources used by the clients once you are done with them. #### Parameters * `server` The server address. @@ -317,6 +398,28 @@ Gets the HTTPS client for the specified server and port. The HTTPS client.
+### `cleanup` + +```cpp +void cleanup() +``` + +Cleans up the clients and releases the resources used by them. It's necessary to call this function to free up the memory used by the client objects that are created by the library internally. + +
+ +### `getManagedClientCount` + +```cpp +size_t getManagedClientCount() const +``` + +Gets the number of managed clients. The clients are managed in the sense of memory management. + +#### Returns +The number of managed clients. +
+ ### `getIPAddress` ```cpp @@ -355,6 +458,135 @@ This function allows you to set the debug stream for [ArduinoCellular](#class_ar * `stream` A pointer to the Stream object that will be used as the debug stream.
+### `getSimStatus` + +```cpp +SimStatus getSimStatus() +``` + +Gets the SIM card status. + +#### Returns +The SIM card status. +
+ +# class `ManagedTinyGsmClient` + +```cpp +class ManagedTinyGsmClient + : public TinyGsmClient +``` + +A managed client for TinyGSM that automatically handles socket allocation and release. This class allows you to create multiple clients without worrying about socket management. It uses a static bit field to track used sockets and provides methods to lock and unlock the sockets. + +## Summary + + Members | Descriptions +--------------------------------|--------------------------------------------- +| [`ManagedTinyGsmClient`](#class_managed_tiny_gsm_client_1ae8426b2b9cbc4fd5cc42fc63b29c4765) | Constructs a [ManagedTinyGsmClient](#class_managed_tiny_gsm_client) with the specified modem. | +| [`ManagedTinyGsmClient`](#class_managed_tiny_gsm_client_1afaea0452624a337c1b9833be6ccb5432) | Copy constructor for [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). This constructor allocates a new socket for the copied client. | +| [`operator=`](#class_managed_tiny_gsm_client_1af87b0fec92e954c59b7a660f2fb4e8f4) | Assignment operator for [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). | +| [`ManagedTinyGsmClient`](#class_managed_tiny_gsm_client_1a50389bc67386357c251be242f8a5edb1) | Move constructor for [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). | +| [`operator=`](#class_managed_tiny_gsm_client_1aadcf674f40d9af11dae82fb85dde41d4) | Move assignment operator for [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). This operator transfers ownership of the socket from the other client to this one. | +| [`~ManagedTinyGsmClient`](#class_managed_tiny_gsm_client_1af227af40b0e787ef8323c5346b69679a) | Destructor for [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). Releases the socket if it is valid. | +| [`getSocketId`](#class_managed_tiny_gsm_client_1a8c1bd76a1728ddefacfdd30c81ac70d7) | Get the socket ID for this client. The maximum number of sockets is defined by TINY_GSM_MUX_COUNT. If the client is invalid, the socketId will be -1. | +| [`isValid`](#class_managed_tiny_gsm_client_1a62617c1a63c68e4cd31a4fcca52fe229) | Check if the client is valid. A client is valid if it has a socket ID >= 0. This is useful to check if the client can be used for network operations. | + +## Members + +### `ManagedTinyGsmClient` + +```cpp +ManagedTinyGsmClient(TinyGsm & modem) +``` + +Constructs a [ManagedTinyGsmClient](#class_managed_tiny_gsm_client) with the specified modem. + +#### Parameters +* `modem` The TinyGsm modem to use. +
+ +### `ManagedTinyGsmClient` + +```cpp +ManagedTinyGsmClient(const ManagedTinyGsmClient & other) +``` + +Copy constructor for [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). This constructor allocates a new socket for the copied client. +#### Parameters +* `other` The other [ManagedTinyGsmClient](#class_managed_tiny_gsm_client) to copy from. Note: If the other client is invalid, this will also create an invalid client. The socketId will be -1. +
+ +### `operator=` + +```cpp +ManagedTinyGsmClient & operator=(const ManagedTinyGsmClient & other) +``` + +Assignment operator for [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). +#### Parameters +* `other` The other [ManagedTinyGsmClient](#class_managed_tiny_gsm_client) to copy from. + +#### Returns +A reference to this [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). +
+ +### `ManagedTinyGsmClient` + +```cpp +ManagedTinyGsmClient( ManagedTinyGsmClient && other) +``` + +Move constructor for [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). +#### Parameters +* `other` The other [ManagedTinyGsmClient](#class_managed_tiny_gsm_client) to move from. +
+ +### `operator=` + +```cpp +ManagedTinyGsmClient & operator=( ManagedTinyGsmClient && other) +``` + +Move assignment operator for [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). This operator transfers ownership of the socket from the other client to this one. +#### Parameters +* `other` The other [ManagedTinyGsmClient](#class_managed_tiny_gsm_client) to move from. + +#### Returns +A reference to this [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). +
+ +### `~ManagedTinyGsmClient` + +```cpp +~ManagedTinyGsmClient() +``` + +Destructor for [ManagedTinyGsmClient](#class_managed_tiny_gsm_client). Releases the socket if it is valid. +
+ +### `getSocketId` + +```cpp +inline int getSocketId() const +``` + +Get the socket ID for this client. The maximum number of sockets is defined by TINY_GSM_MUX_COUNT. If the client is invalid, the socketId will be -1. +#### Returns +The socket ID (0 - (TINY_GSM_MUX_COUNT - 1) or -1 if invalid). +
+ +### `isValid` + +```cpp +inline bool isValid() const +``` + +Check if the client is valid. A client is valid if it has a socket ID >= 0. This is useful to check if the client can be used for network operations. +#### Returns +True if the client is valid, false otherwise. +
+ # class `ModemInterface` ```cpp diff --git a/examples/MultipleConnections/MultipleConnections.ino b/examples/MultipleConnections/MultipleConnections.ino new file mode 100644 index 0000000..5517de3 --- /dev/null +++ b/examples/MultipleConnections/MultipleConnections.ino @@ -0,0 +1,79 @@ +/** + * This example shows how to use the connection management features of the ArduinoCellular library + * to maintain multiple HTTP and HTTPS clients. + * This is useful if e.g. you want to use ArduinoIoTCloud or other services that require + * a dedicated client while making custom HTTP requests to other servers. + * + * Instructions: + * 1. Insert a SIM card with or without PIN code in the Arduino Pro 4G Module. + * 2. Provide sufficient power to the Arduino Pro 4G Module. Ideally, use a 5V power supply + * with a current rating of at least 2A and connect it to the VIN and GND pins. + * 3. Specify the APN, login, and password for your cellular network provider. + * 4. Upload the sketch to the connected Arduino board. + * 5. Open the serial monitor to view the output. + * + * Initial author: Sebastian Romero +*/ + +#include +#include "ArduinoCellular.h" +#include + +#define SECRET_PINNUMBER "" +#define SECRET_GPRS_LOGIN "" +#define SECRET_GPRS_APN "gprs.swisscom.ch" +#define SECRET_GPRS_PASSWORD "" + +constexpr int SSL_PORT = 443; + +ArduinoCellular cellular = ArduinoCellular(); + +void getResource(HttpClient &client, const char *resource) { + Serial.println("Making GET request..."); + client.get(resource); + int statusCode = client.responseStatusCode(); + + if (statusCode < 0) { + Serial.print("Error Response Code: "); + Serial.println(statusCode); + return; + } + Serial.print("Response: "); + Serial.println(client.responseBody()); +} + + +void setup(){ + Serial.begin(115200); + while (!Serial); + + cellular.begin(); + + if(String(SECRET_PINNUMBER).length() > 0 && !cellular.unlockSIM(SECRET_PINNUMBER)){ + Serial.println("Failed to unlock SIM card."); + while(true); // Stop here + } + + Serial.println("Connecting..."); + if(!cellular.connect(SECRET_GPRS_APN, SECRET_GPRS_LOGIN, SECRET_GPRS_PASSWORD)){ + Serial.println("Failed to connect to the network."); + while(true); // Stop here + } + Serial.println("Connected!"); + + HttpClient client1 = cellular.getHTTPSClient("vsh.pp.ua", SSL_PORT); + HttpClient client2 = cellular.getHTTPSClient("wttr.in", SSL_PORT); + Serial.print("Managed client count: "); + Serial.println(cellular.getManagedClientCount()); + + getResource(client1, "/TinyGSM/logo.txt"); + getResource(client2, "/?format=3"); + client1.stop(); + client2.stop(); + cellular.cleanup(); // Clean up connections after use + + Serial.print("Managed client count: "); + Serial.println(cellular.getManagedClientCount()); +} + +void loop(){} diff --git a/src/ArduinoCellular.cpp b/src/ArduinoCellular.cpp index a4f0d2b..206b7e3 100644 --- a/src/ArduinoCellular.cpp +++ b/src/ArduinoCellular.cpp @@ -1,6 +1,7 @@ #include "ArduinoCellular.h" + #if defined(ARDUINO_ARCH_MBED) #include "Watchdog.h" #endif @@ -8,15 +9,19 @@ unsigned long ArduinoCellular::getTime() { int year, month, day, hour, minute, second; float tz; - modem.getNetworkTime(&year, &month, &day, &hour, &minute, &second, &tz); + Modem.getNetworkTime(&year, &month, &day, &hour, &minute, &second, &tz); return Time(year, month, day, hour, minute, second).getUNIXTimestamp(); } ArduinoCellular::ArduinoCellular() { } +ArduinoCellular::~ArduinoCellular() { + cleanup(); +} + void ArduinoCellular::begin() { - modem.init(); + Modem.init(); String modemInfo = this ->sendATCommand("I"); if(modemInfo.indexOf("EC200A") > 0){ @@ -28,15 +33,15 @@ void ArduinoCellular::begin() { } // Set GSM module to text mode - modem.sendAT("+CMGF=1"); - modem.waitResponse(); + Modem.sendAT("+CMGF=1"); + Modem.waitResponse(); - modem.sendAT(GF("+CSCS=\"GSM\"")); - modem.waitResponse(); + Modem.sendAT(GF("+CSCS=\"GSM\"")); + Modem.waitResponse(); // Send intrerupt when SMS has been received - modem.sendAT("+CNMI=2,1,0,0,0"); - modem.waitResponse(); + Modem.sendAT("+CNMI=2,1,0,0,0"); + Modem.waitResponse(); #if defined(ARDUINO_CELLULAR_BEARSSL) ArduinoBearSSL.onGetTime(ArduinoCellular::getTime); @@ -103,7 +108,7 @@ Geolocation ArduinoCellular::getGPSLocation(unsigned long timeout){ unsigned long startTime = millis(); while((latitude == 0.00000 || longitude == 0.00000) && (millis() - startTime < timeout)) { - modem.getGPS(&latitude, &longitude); + Modem.getGPS(&latitude, &longitude); delay(1000); } @@ -122,7 +127,7 @@ Geolocation ArduinoCellular::getGPSLocation(unsigned long timeout){ Time ArduinoCellular::getGPSTime(){ int year, month, day, hour, minute, second; - modem.getGPSTime(&year, &month, &day, &hour, &minute, &second); + Modem.getGPSTime(&year, &month, &day, &hour, &minute, &second); return Time(year, month, day, hour, minute, second); } @@ -134,22 +139,22 @@ Time ArduinoCellular::getCellularTime(){ int minute = 0; int second = 0; float tz; - if (modem.NTPServerSync() == 0) { - modem.getNetworkTime(&year, &month, &day, &hour, &minute, &second, &tz); + if (Modem.NTPServerSync() == 0) { + Modem.getNetworkTime(&year, &month, &day, &hour, &minute, &second, &tz); } return Time(year, month, day, hour, minute, second); } void ArduinoCellular::sendSMS(String number, String message){ - modem.sendAT("+CMGF=1"); - modem.waitResponse(1000); - modem.sendAT(GF("+CMGS=\""), number, GF("\"")); - if (modem.waitResponse(GF(">")) != 1) { } - modem.stream->print(message); // Actually send the message - modem.stream->write(static_cast(0x1A)); // Terminate the message - modem.stream->flush(); - auto response = modem.waitResponse(10000L); + Modem.sendAT("+CMGF=1"); + Modem.waitResponse(1000); + Modem.sendAT(GF("+CMGS=\""), number, GF("\"")); + if (Modem.waitResponse(GF(">")) != 1) { } + Modem.stream->print(message); // Actually send the message + Modem.stream->write(static_cast(0x1A)); // Terminate the message + Modem.stream->flush(); + auto response = Modem.waitResponse(10000L); if(this->debugStream != nullptr){ this->debugStream->println("Response: " + String(response)); @@ -158,40 +163,60 @@ void ArduinoCellular::sendSMS(String number, String message){ IPAddress ArduinoCellular::getIPAddress(){ - return modem.localIP(); + return Modem.localIP(); } int ArduinoCellular::getSignalQuality(){ - return modem.getSignalQuality(); + return Modem.getSignalQuality(); } TinyGsmClient ArduinoCellular::getNetworkClient(){ - return TinyGsmClient(modem); + return ManagedTinyGsmClient(Modem); } HttpClient ArduinoCellular::getHTTPClient(const char * server, const int port){ - return HttpClient(* new TinyGsmClient(modem), server, port); + return HttpClient(* new ManagedTinyGsmClient(Modem), server, port); } #if defined(ARDUINO_CELLULAR_BEARSSL) HttpClient ArduinoCellular::getHTTPSClient(const char * server, const int port){ - return HttpClient(* new BearSSLClient(* new TinyGsmClient(modem)), server, port); + auto gsmClient = std::make_unique(Modem); + auto sslClient = std::make_unique(*gsmClient); + auto& sslRef = *sslClient; + + managedGsmClients.push_back(std::move(gsmClient)); + managedSslClients.push_back(std::move(sslClient)); + return HttpClient(sslRef, server, port); } BearSSLClient ArduinoCellular::getSecureNetworkClient(){ - return BearSSLClient(* new TinyGsmClient(modem)); + return BearSSLClient(* new ManagedTinyGsmClient(Modem)); } #endif +void ArduinoCellular::cleanup() { + /* + It's necessary to to manage the lifetime of the clients created by the library + because the HttpClient and also BearSSLClient classes expect callers to manage the lifetime of the clients. + For convenience, the library provides factory functions that allocate these objects on behalf of the caller. + */ + managedSslClients.clear(); // Destroys BearSSLClient instances + managedGsmClients.clear(); // Destroys ManagedTinyGsmClient instances +} + +size_t ArduinoCellular::getManagedClientCount() const { + return managedSslClients.size(); +} + bool ArduinoCellular::isConnectedToOperator(){ - return modem.isNetworkConnected(); + return Modem.isNetworkConnected(); } bool ArduinoCellular::connectToGPRS(const char * apn, const char * gprsUser, const char * gprsPass){ if(this->debugStream != nullptr){ this->debugStream->println("Connecting to 4G network..."); } - while(!modem.gprsConnect(apn, gprsUser, gprsPass)) { + while(!Modem.gprsConnect(apn, gprsUser, gprsPass)) { if(this->debugStream != nullptr){ this->debugStream->print("."); } @@ -201,11 +226,11 @@ bool ArduinoCellular::connectToGPRS(const char * apn, const char * gprsUser, con } bool ArduinoCellular::isConnectedToInternet(){ - return modem.isGprsConnected(); + return Modem.isGprsConnected(); } SimStatus ArduinoCellular::getSimStatus(){ - int simStatus = modem.getSimStatus(); + int simStatus = Modem.getSimStatus(); if(this->debugStream != nullptr){ this->debugStream->println("SIM Status: " + String(simStatus)); } @@ -224,12 +249,12 @@ SimStatus ArduinoCellular::getSimStatus(){ } bool ArduinoCellular::unlockSIM(String pin){ - int simStatus = modem.getSimStatus(); + int simStatus = Modem.getSimStatus(); if(simStatus == SIM_LOCKED) { if(this->debugStream != nullptr){ this->debugStream->println("Unlocking SIM..."); } - return modem.simUnlock(pin.c_str()); + return Modem.simUnlock(pin.c_str()); } else if(simStatus == SIM_ERROR || simStatus == SIM_ANTITHEFT_LOCKED) { return false; @@ -242,7 +267,7 @@ bool ArduinoCellular::awaitNetworkRegistration(bool waitForever){ if(this->debugStream != nullptr){ this->debugStream->println("Waiting for network registration..."); } - while (!modem.waitForNetwork(waitForNetworkTimeout)) { + while (!Modem.waitForNetwork(waitForNetworkTimeout)) { if(!waitForever) { return false; @@ -283,13 +308,13 @@ bool ArduinoCellular::enableGPS(bool assisted){ return false; } - return modem.enableGPS(); + return Modem.enableGPS(); } String ArduinoCellular::sendATCommand(const char * command, unsigned long timeout){ String response; - modem.sendAT(command); - modem.waitResponse(timeout, response); + Modem.sendAT(command); + Modem.waitResponse(timeout, response); return response; } @@ -401,7 +426,7 @@ std::vector parseSMSData(const String& data) { } String ArduinoCellular::sendUSSDCommand(const char * command){ - return modem.sendUSSD(command); + return Modem.sendUSSD(command); } std::vector ArduinoCellular::getReadSMS(){ diff --git a/src/ArduinoCellular.h b/src/ArduinoCellular.h index 8f32de5..178caf6 100644 --- a/src/ArduinoCellular.h +++ b/src/ArduinoCellular.h @@ -16,6 +16,7 @@ #include #include +#include #if defined __has_include #if !__has_include () @@ -30,6 +31,7 @@ #include #include +#include "ManagedTinyGsmClient.h" /** * @enum ModemModel @@ -100,6 +102,37 @@ class ArduinoCellular { */ ArduinoCellular(); + /** + * @brief Destructor for the ArduinoCellular class. + * Cleans up any resources used by the class. + */ + ~ArduinoCellular(); + + /** + * @brief Deleted copy constructor and assignment operator to prevent copying. + * unique_ptr is used to manage the lifetime of the clients but they cannot be copied. + */ + ArduinoCellular(const ArduinoCellular&) = delete; + + /** + * @brief Deleted assignment operator to prevent copying. + * unique_ptr is used to manage the lifetime of the clients but they cannot be copied. + */ + ArduinoCellular& operator=(const ArduinoCellular&) = delete; + + /** + * @brief Move constructor for the ArduinoCellular class. + * Allows moving the instance to another instance. + */ + ArduinoCellular(ArduinoCellular&&) = default; + + /** + * @brief Move assignment operator for the ArduinoCellular class. + * Allows moving the instance to another instance. + * @return A reference to the moved instance. + */ + ArduinoCellular& operator=(ArduinoCellular&&) = default; + /** * @brief Initializes the modem. * This function must be called before using any other functions in the library. @@ -218,8 +251,12 @@ class ArduinoCellular { /** - * @brief Gets the Network client. (OSI Layer 3) - * @return The GSM client. + * @brief Gets a new Network client. (OSI Layer 3) + * The library automatically manages the sockets, so you can create multiple clients + * without worrying about socket management. You should ensure that you release the client when you are done with it. + * It's possible that the client is invalid if no sockets are available. + * This is indicated by the isValid() method and the socketId will be -1. + * @return A GSM client object that can be used to connect to a server. */ TinyGsmClient getNetworkClient(); @@ -232,7 +269,9 @@ class ArduinoCellular { #endif /** - * @brief Gets the HTTP client for the specified server and port. + * @brief Gets a HTTP client for the specified server and port. + * The maximum number of HTTP clients is limited by the number of sockets available. + * Call `cleanup()` to release the resources used by the clients once you are done with them. * @param server The server address. * @param port The server port. * @return The HTTP client. @@ -240,13 +279,29 @@ class ArduinoCellular { HttpClient getHTTPClient(const char * server, const int port); /** - * @brief Gets the HTTPS client for the specified server and port. + * @brief Gets a HTTPS client for the specified server and port. + * The maximum number of HTTP clients is limited by the number of sockets available. + * Call `cleanup()` to release the resources used by the clients once you are done with them. * @param server The server address. * @param port The server port. * @return The HTTPS client. */ HttpClient getHTTPSClient(const char * server, const int port); + /** + * @brief Cleans up the clients and releases the resources used by them. + * It's necessary to call this function to free up the memory used by the client + * objects that are created by the library internally. + */ + void cleanup(); + + /** + * @brief Gets the number of managed clients. + * The clients are managed in the sense of memory management. + * @return The number of managed clients. + */ + size_t getManagedClientCount() const; + /** * @brief Gets the local IP address. * @return The local IP address. @@ -276,6 +331,10 @@ class ArduinoCellular { SimStatus getSimStatus(); private: + // Each instance manages its own connections + std::vector> managedGsmClients; + std::vector> managedSslClients; + bool connectToGPRS(const char * apn, const char * gprsUser, const char * gprsPass); diff --git a/src/ManagedTinyGsmClient.cpp b/src/ManagedTinyGsmClient.cpp new file mode 100644 index 0000000..2ed9696 --- /dev/null +++ b/src/ManagedTinyGsmClient.cpp @@ -0,0 +1,82 @@ +#include "ManagedTinyGsmClient.h" + +// Static member definitions +uint32_t ManagedTinyGsmClient::usedSockets = 0; +bool ManagedTinyGsmClient::socketMutex = false; + +void ManagedTinyGsmClient::lockSockets() { + // Simple spinlock - not ideal but works for basic protection + while (socketMutex) { + delay(1); // Small delay to prevent busy waiting + } + socketMutex = true; +} + +void ManagedTinyGsmClient::unlockSockets() { + socketMutex = false; +} + +int ManagedTinyGsmClient::allocateSocket() { + lockSockets(); + + for (int i = 0; i < TINY_GSM_MUX_COUNT; i++) { + uint32_t mask = 1UL << i; + if (!(usedSockets & mask)) { + usedSockets |= mask; // Set bit i + unlockSockets(); + return i; + } + } + + unlockSockets(); + return -1; // No available sockets +} + +void ManagedTinyGsmClient::releaseSocket(int socketId) { + if (socketId >= 0 && socketId < TINY_GSM_MUX_COUNT) { + lockSockets(); + uint32_t mask = 1UL << socketId; + usedSockets &= ~mask; // Clear bit socketId + unlockSockets(); + } +} + +ManagedTinyGsmClient::ManagedTinyGsmClient(TinyGsm& modem) + : socketId(allocateSocket()), TinyGsmClient(modem, socketId) { + // Note: If socketId is -1, the client is invalid +} + +ManagedTinyGsmClient::ManagedTinyGsmClient(const ManagedTinyGsmClient& other) + : socketId(allocateSocket()), TinyGsmClient(other) { + // Copy constructor allocates a new socket + // Note: If socketId is -1, the client is invalid +} + +ManagedTinyGsmClient& ManagedTinyGsmClient::operator=(const ManagedTinyGsmClient& other) { + if (this != &other) { + TinyGsmClient::operator=(other); + // Keep our own socket - don't change it + } + return *this; +} + +ManagedTinyGsmClient::ManagedTinyGsmClient(ManagedTinyGsmClient&& other) + : socketId(other.socketId), TinyGsmClient(other) { + other.socketId = -1; // Mark other as moved-from +} + +ManagedTinyGsmClient& ManagedTinyGsmClient::operator=(ManagedTinyGsmClient&& other) { + if (this != &other) { + // Release our current socket + releaseSocket(socketId); + + TinyGsmClient::operator=(other); + socketId = other.socketId; + other.socketId = -1; // Mark other as moved-from + } + return *this; +} + +ManagedTinyGsmClient::~ManagedTinyGsmClient() { + releaseSocket(socketId); +} \ No newline at end of file diff --git a/src/ManagedTinyGsmClient.h b/src/ManagedTinyGsmClient.h new file mode 100644 index 0000000..3e09cfe --- /dev/null +++ b/src/ManagedTinyGsmClient.h @@ -0,0 +1,83 @@ +#ifndef MANAGED_TINY_GSM_CLIENT_H +#define MANAGED_TINY_GSM_CLIENT_H + +#include "ModemInterface.h" + +/** + * @class ManagedTinyGsmClient + * @brief A managed client for TinyGSM that automatically handles socket allocation and release. + * This class allows you to create multiple clients without worrying about socket management. + * It uses a static bit field to track used sockets and provides methods to lock and unlock the sockets. + */ +class ManagedTinyGsmClient : public TinyGsmClient { +private: + int socketId; + static uint32_t usedSockets; // Bit field to track sockets 0-(TINY_GSM_MUX_COUNT-1) + static bool socketMutex; // Simple mutex flag + + static int allocateSocket(); + static void releaseSocket(int socketId); + static void lockSockets(); + static void unlockSockets(); + +public: + /** + * @brief Constructs a ManagedTinyGsmClient with the specified modem. + * @param modem The TinyGsm modem to use. + */ + ManagedTinyGsmClient(TinyGsm& modem); + + /** + * Copy constructor for ManagedTinyGsmClient. + * This constructor allocates a new socket for the copied client. + * @param other The other ManagedTinyGsmClient to copy from. + * Note: If the other client is invalid, this will also create an invalid client. + * The socketId will be -1. + */ + ManagedTinyGsmClient(const ManagedTinyGsmClient& other); + + /** + * Assignment operator for ManagedTinyGsmClient. + * @param other The other ManagedTinyGsmClient to copy from. + * @return A reference to this ManagedTinyGsmClient. + */ + ManagedTinyGsmClient& operator=(const ManagedTinyGsmClient& other); + + /** + * Move constructor for ManagedTinyGsmClient. + * @param other The other ManagedTinyGsmClient to move from. + */ + ManagedTinyGsmClient(ManagedTinyGsmClient&& other); + + /** + * Move assignment operator for ManagedTinyGsmClient. + * This operator transfers ownership of the socket from the other client to this one. + * @param other The other ManagedTinyGsmClient to move from. + * @return A reference to this ManagedTinyGsmClient. + */ + ManagedTinyGsmClient& operator=(ManagedTinyGsmClient&& other); + + /** + * Destructor for ManagedTinyGsmClient. + * Releases the socket if it is valid. + */ + ~ManagedTinyGsmClient(); + + /** + * Get the socket ID for this client. + * The maximum number of sockets is defined by TINY_GSM_MUX_COUNT. + * If the client is invalid, the socketId will be -1. + * @return The socket ID (0 - (TINY_GSM_MUX_COUNT - 1) or -1 if invalid). + */ + int getSocketId() const { return socketId; } + + /** + * Check if the client is valid. + * A client is valid if it has a socket ID >= 0. + * This is useful to check if the client can be used for network operations. + * @return True if the client is valid, false otherwise. + */ + bool isValid() const { return socketId >= 0; } +}; + +#endif // MANAGED_TINY_GSM_CLIENT_H diff --git a/src/ModemInterface.cpp b/src/ModemInterface.cpp index a8777a0..19669e4 100644 --- a/src/ModemInterface.cpp +++ b/src/ModemInterface.cpp @@ -16,9 +16,9 @@ // we need to make sure that the Modem object is initialised before anything else in the sketch to avoid issues with the TinyGSM library // the init_priority attribute is used to set the priority of the constructor, the lower the number the higher the priority (101 to 65535) // for more information see https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html - __attribute__ ((init_priority (101))) ModemInterface modem(debugger, PORTENTA_C33_MODEM_ON_PIN); + __attribute__ ((init_priority (101))) ModemInterface Modem(debugger, PORTENTA_C33_MODEM_ON_PIN); #else - __attribute__ ((init_priority (101))) ModemInterface modem(Serial1_FC, PORTENTA_C33_MODEM_ON_PIN); + __attribute__ ((init_priority (101))) ModemInterface Modem(Serial1_FC, PORTENTA_C33_MODEM_ON_PIN); #endif #elif defined(ARDUINO_PORTENTA_H7_M7) || defined(CORE_CM4) @@ -27,9 +27,11 @@ // P602/P110/P603/P604 -> Serial1 #ifdef DUMP_AT_COMMANDS StreamDebugger debugger(Serial1, Serial); - __attribute__ ((init_priority (101))) ModemInterface modem(debugger, PinNameToIndex(PORTENTA_H7_MODEM_ON_PIN)); + __attribute__ ((init_priority (101))) ModemInterface Modem(debugger, PinNameToIndex(PORTENTA_H7_MODEM_ON_PIN)); #else - __attribute__ ((init_priority (101))) ModemInterface modem(Serial1, PinNameToIndex(PORTENTA_H7_MODEM_ON_PIN)); + __attribute__ ((init_priority (101))) ModemInterface Modem(Serial1, PinNameToIndex(PORTENTA_H7_MODEM_ON_PIN)); #endif +#else + #error "Unsupported board. Please define the modem interface for your board." #endif diff --git a/src/ModemInterface.h b/src/ModemInterface.h index 14086a6..d23b252 100644 --- a/src/ModemInterface.h +++ b/src/ModemInterface.h @@ -72,6 +72,6 @@ class ModemInterface : public TinyGsmBG96 { /** * @brief The global modem object. */ -extern ModemInterface modem; +extern ModemInterface Modem; #endif