diff --git a/README.md b/README.md index df14aea0..a03bf227 100644 --- a/README.md +++ b/README.md @@ -115,15 +115,15 @@ See this Arduino-Pico SDK [documentation](https://arduino-pico.readthedocs.io/en For new `Firebase` users, please read the [Project Preparation and Setup](#project-preparation-and-setup) section for preparing the Firebase project. -For new library user that the project was setup and prepared, see the [bare minimun examples](/examples/BareMinimum/) to start using this library with minimum code that requires by this library. +For new library user that the project was setup and prepared, see the [bare minimun examples](/examples/BareMinimum/) to start using this library with minimum code that is requiresd by this library. For more examples, please click [here](/examples/). ### Working Principle -This library can be used in two modes i.e. async mode and await or sinc mode. +This library can be used in two modes i.e. async mode and await or sync mode. -With async mode, the task will store in the FIFO queue. The result of the running task can be obtained via the callback function or the proxy object that assign when calling the functions. +With async mode, the task will be stored in the FIFO queue. The result of the running task can be obtained via the callback function or the proxy object that assign when calling the functions. The `AsyncClientClass` is the proxy class that provides the queue for async tasks and also the information of task process when working in await or sync mode. @@ -712,4 +712,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of `THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.` -*Last updated 2025-05-29 UTC.* \ No newline at end of file +*Last updated 2025-05-29 UTC.* diff --git a/examples/BareMinimum/RealtimeDatabase/RealTimeDatabase_Async.ino b/examples/BareMinimum/RealtimeDatabase/RealTimeDatabase_Async.ino new file mode 100644 index 00000000..4dc09be5 --- /dev/null +++ b/examples/BareMinimum/RealtimeDatabase/RealTimeDatabase_Async.ino @@ -0,0 +1,218 @@ +/** + * The bare minimum code example for using the Realtime Database service. + * + * The generally required steps are explained below. + * + * Step 1. Include the network, SSL client, and Firebase libraries. + * ================================================================= + * + * Step 2. Define the user functions that are required for library usage. + * ======================================================================= + * + * Step 3. Define the authentication configuration (identifier) object. + * ====================================================================== + * In the Firebase and Google Cloud REST APIs, auth tokens are used for authentication and authorization. + * + * The auth token is a short-lived token that expires in 60 minutes and needs to be refreshed or re-created. + * + * For some special use cases, services may provide non-authenticated access, e.g., using a database secret + * in the Realtime Database, or setting security rules in the Realtime Database, Firestore, and Firebase Storage to allow public read/write access. + * + * UserAuth (user authentication with email/password) is the basic authentication method for the Realtime Database, + * Firebase Storage, and Firestore services, except for some Firestore services that are involved with Google Cloud. + * + * The UserAuth object stores the email, password, and API key for the authentication process. + * + * For Google Cloud services, e.g., Cloud Storage and Cloud Functions, the highest authentication level is required. + * For these cases, the ServiceAuth (OAuth2.0) and AccessToken classes are used. + * + * CustomAuth provides the same authentication level as UserAuth, but allows for a custom UID and claims. + * + * Step 4. Define the authentication handler object. + * ================================================== + * The FirebaseApp object works as the authentication handler. + * It also maintains authentication and re-authentication when you place `FirebaseApp::loop()` inside your main loop. + * + * Step 5. Define the SSL client. + * ================================ + * The SSL client handles the server connection and data transfer. + * + * In this bare minimum example, we use only one SSL client for all processes. + * For some use cases, e.g., a Realtime Database Stream connection, you may have to define a separate SSL client for that stream. + * + * Step 6. Define the Async Client. + * ================================ + * This class is used for any function where server data transfer is involved. + * It stores all synchronous and asynchronous tasks in its queue. + * + * It requires the SSL client and network configuration data in its constructor for handling network re-connections + * (e.g., Wi-Fi and GSM), checking network status, and managing the server connection and data transfer processes. + * + * This design makes the library reliable and allows it to operate precisely under various server and network conditions. + * + * Step 7. Define the class that provides the Firebase/Google Cloud service. + * ============================================================================ + * The Firebase/Google Cloud service classes provide the member functions that work with the AsyncClient. + * + * Step 8. Start the authentication process. + * ========================================== + * At this step, the authentication credentials will be used to generate auth tokens by + * calling `initializeApp`. + * + * This allows us to use different authentication methods for each Firebase/Google Cloud service by using different + * FirebaseApp (authentication handler) objects. + * + * The workflow of the authentication process is as follows: + * + * ----------------------------------------------------------------------------------------------------------------- + * Setup | FirebaseApp [account credentials/tokens] ---> InitializeApp (w/ or w/o timeout) ---> FirebaseApp::getApp + * ----------------------------------------------------------------------------------------------------------------- + * Loop | FirebaseApp::loop ---> FirebaseApp::ready ---> Firebase Service API [auth token] + * ----------------------------------------------------------------------------------------------------------------- + * + * Step 9. Bind the FirebaseApp (authentication handler) with your Firebase/Google Cloud service object. + * ========================================================================================================= + * This allows us to use different authentication methods for each Firebase/Google Cloud service. + * + * It is easy to bind, unbind, or change the authentication method for different Firebase/Google Cloud service APIs. + * + * Step 10. Set the Realtime Database URL (for the Realtime Database only). + * ======================================================================== + * + * Step 11. Maintain authentication and async tasks in the loop. + * =============================================================== + * This is required for the authentication/re-authentication process and to keep async tasks running. + * + * Step 12. Check the authentication status before use. + * ==================================================== + * Before calling a Firebase/Google Cloud service function, the `FirebaseApp::ready()` function of the associated authentication handler + * should return true. + * + * Step 13. Process the results of async tasks at the end of the loop. + * ===================================================================== + * This is only required when an `AsyncResult` object was assigned to a Firebase/Google Cloud service function. + */ + +// Step 1 + +#define ENABLE_USER_AUTH +#define ENABLE_DATABASE + +#include +#include +#include + +// Step 2 +void asyncCB(AsyncResult &aResult); +void processData(AsyncResult &aResult); + +// Step 3 +UserAuth user_auth("Web_API_KEY", "USER_EMAIL", "USER_PASSWORD"); + +// Step 4 +FirebaseApp app; + +// Step 5 +// SSL client async tasks. +WiFiClientSecure ssl_client; + +// Step 6 +// Use AsyncClient async tasks. +using AsyncClient = AsyncClientClass; +AsyncClient async_client(ssl_client); + +// Step 7 +RealtimeDatabase Database; + +bool onetimeTest = false; + +// The Optional proxy object that provides the data/information +// when used in async mode without callback. +AsyncResult dbResult; + +void setup() +{ + Serial.begin(115200); + + WiFi.begin("WIFI_AP", "WIFI_PASSWORD"); + + Serial.print("Connecting to Wi-Fi"); + while (WiFi.status() != WL_CONNECTED) + { + Serial.print("."); + delay(300); + } + Serial.println(); + Serial.print("Connected with IP: "); + Serial.println(WiFi.localIP()); + Serial.println(); + + // The SSL client options depend on the SSL client used. + + // Skip certificate verification + ssl_client.setInsecure(); + + // ESP8266 Set buffer size + // ssl_client.setBufferSizes(4096, 1024); + + // Step 8 + initializeApp(async_client, app, getAuth(user_auth), processData, "🔐 authTask"); + + // Step 9 + app.getApp(Database); + + // Step 10 + Database.url("DATABASE_URL"); +} + +void loop() +{ + // Step 11 + app.loop(); + + // Step 12 + if (app.ready() && !onetimeTest) + { + onetimeTest = true; + + // Realtime Database set value. + // ============================ + + // Async call with callback function + Database.set(async_client, "/examples/BareMinimum/data/set1", "abc", processData, "RealtimeDatabase_SetTask"); + + // Async call with AsyncResult for returning result. + Database.set(async_client, "/examples/BareMinimum/data/set2", true, dbResult); + + // Realtime Database get value. + // ============================ + + // Async call with callback function + Database.get(async_client, "/examples/BareMinimum/data/set1", processData, false, "RealtimeDatabase_GetTask"); + + // Async call with AsyncResult for returning result. + Database.get(async_client, "/examples/BareMinimum/data/set2", dbResult, false); + } + + // Step 13 + processData(dbResult); +} + +void processData(AsyncResult &aResult) +{ + // Exits when no result available when calling from the loop. + if (!aResult.isResult()) + return; + + if (aResult.isEvent()) + Firebase.printf("Event task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.eventLog().message().c_str(), aResult.eventLog().code()); + + if (aResult.isDebug()) + Firebase.printf("Debug task: %s, msg: %s\n", aResult.uid().c_str(), aResult.debug().c_str()); + + if (aResult.isError()) + Firebase.printf("Error task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.error().message().c_str(), aResult.error().code()); + + if (aResult.available()) + Firebase.printf("task: %s, payload: %s\n", aResult.uid().c_str(), aResult.c_str()); +} diff --git a/examples/BareMinimum/RealtimeDatabase/RealtimeDatabase.ino b/examples/BareMinimum/RealtimeDatabase/RealtimeDatabase.ino deleted file mode 100644 index 5d1fa2f9..00000000 --- a/examples/BareMinimum/RealtimeDatabase/RealtimeDatabase.ino +++ /dev/null @@ -1,248 +0,0 @@ -/** - * The bare minimum code example for using Realtime Database service. - * - * The steps which are generally required are explained below. - * - * Step 1. Include the network, SSL client and Firebase libraries. - * =============================================================== - * - * Step 2. Define the user functions that are required for library usage. - * ===================================================================== - * - * Step 3. Define the authentication config (identifier) class. - * ============================================================ - * In the Firebase/Google Cloud services REST APIs, the auth tokens are used for authentication/authorization. - * - * The auth token is a short-lived token that will be expired in 60 minutes and need to be refreshed or re-created when it expired. - * - * There can be some special use case that some services provided the non-authentication usages e.g. using database secret - * in Realtime Database, setting the security rules in Realtime Database, Firestore and Firebase Storage to allow public read/write access. - * - * The UserAuth (user authentication with email/password) is the basic authentication for Realtime Database, - * Firebase Storage and Firestore services except for some Firestore services that involved with the Google Cloud services. - * - * It stores the email, password and API keys for authentication process. - * - * In Google Cloud services e.g. Cloud Storage and Cloud Functions, the higest authentication level is required and - * the ServiceAuth class (OAuth2.0 authen) and AccessToken class will be use for this case. - * - * While the CustomAuth provides the same authentication level as user authentication unless it allows the custom UID and claims. - * - * Step 4. Define the authentication handler class. - * ================================================ - * The FirebaseApp actually works as authentication handler. - * It also maintains the authentication or re-authentication when you place the FirebaseApp::loop() inside the main loop. - * - * Step 5. Define the SSL client. - * ============================== - * It handles server connection and data transfer works. - * - * In this beare minimum example we use only one SSL client for all processes. - * In some use cases e.g. Realtime Database Stream connection, you may have to define the SSL client for it separately. - * - * Step 6. Define the Async Client. - * ================================ - * This is the class that is used with the functions where the server data transfer is involved. - * It stores all sync/async taks in its queue. - * - * It requires the SSL client and network config (identifier) data for its class constructor for its network re-connection - * (e.g. WiFi and GSM), network connection status checking, server connection, and data transfer processes. - * - * This makes this library reliable and operates precisely under various server and network conditions. - * - * Step 7. Define the class that provides the Firebase/Google Cloud services. - * ========================================================================== - * The Firebase/Google Cloud services classes provide the member functions that works with AsyncClient. - * - * Step 8. Start the authenticate process. - * ======================================== - * At this step, the authentication credential will be used to generate the auth tokens for authentication by - * calling initializeApp. - * - * This allows us to use different authentications for each Firebase/Google Cloud services with different - * FirebaseApps (authentication handler)s. - * - * When calling initializeApp with timeout, the authenication process will begin immediately and wait at this process - * until it finished or timed out. It works in sync mode. - * - * If no timeout was assigned, it will work in async mode. The authentication task will be added to async client queue - * to process later e.g. in the loop by calling FirebaseApp::loop. - * - * The workflow of authentication process. - * - * ----------------------------------------------------------------------------------------------------------------- - * Setup | FirebaseApp [account credentials/tokens] ───> InitializeApp (w/wo timeout) ───> FirebaseApp::getApp - * ----------------------------------------------------------------------------------------------------------------- - * Loop | FirebaseApp::loop ───> FirebaseApp::ready ───> Firebase Service API [auth token] - * --------------------------------------------------------------------------------------------------- - * - * Step 9. Bind the FirebaseApp (authentication handler) with your Firebase/Google Cloud services classes. - * ======================================================================================================== - * This allows us to use different authentications for each Firebase/Google Cloud services. - * - * It is easy to bind/unbind/change the authentication method for different Firebase/Google Cloud services APIs. - * - * Step 10. Set the Realtime Database URL (for Realtime Database only) - * =================================================================== - * - * Step 11. Maintain the authentication and async tasks in the loop. - * ============================================================== - * This is required for authentication/re-authentication process and keeping the async task running. - * - * Step 12. Checking the authentication status before use. - * ======================================================= - * Before calling the Firebase/Google Cloud services functions, the FirebaseApp::ready() of authentication handler that bined to it - * should return true. - * - * Step 13. Process the results of async tasks the end of the loop. - * ============================================================================ - * This requires only when async result was assigned to the Firebase/Google Cloud services functions. - */ - -// Step 1 - -#define ENABLE_USER_AUTH -#define ENABLE_DATABASE - -#include -#include -#include - -// Step 2 -void asyncCB(AsyncResult &aResult); -void processData(AsyncResult &aResult); - -// Step 3 -UserAuth user_auth("Web_API_KEY", "USER_EMAIL", "USER_PASSWORD"); - -// Step 4 -FirebaseApp app; - -// Step 5 -// Use two SSL clients for sync and async tasks for demonstation only. -WiFiClientSecure ssl_client1, ssl_client2; - -// Step 6 -// Use two AsyncClients for sync and async tasks for demonstation only. -using AsyncClient = AsyncClientClass; -AsyncClient async_client1(ssl_client1), async_client2(ssl_client2); - -// Step 7 -RealtimeDatabase Database; - -bool onetimeTest = false; - -// The Optional proxy object that provides the data/information -// when used in async mode without callback. -AsyncResult dbResult; - -void setup() -{ - Serial.begin(115200); - - WiFi.begin("WIFI_AP", "WIFI_PASSWORD"); - - Serial.print("Connecting to Wi-Fi"); - while (WiFi.status() != WL_CONNECTED) - { - Serial.print("."); - delay(300); - } - Serial.println(); - Serial.print("Connected with IP: "); - Serial.println(WiFi.localIP()); - Serial.println(); - - // The SSL client options depend on the SSL client used. - - // Skip certificate verification - ssl_client1.setInsecure(); - ssl_client2.setInsecure(); - - // Set timeout - ssl_client1.setConnectionTimeout(1000); - ssl_client1.setHandshakeTimeout(5); - ssl_client2.setConnectionTimeout(1000); - ssl_client2.setHandshakeTimeout(5); - - // ESP8266 Set buffer size - // ssl_client1.setBufferSizes(4096, 1024); - // ssl_client2.setBufferSizes(4096, 1024); - - // Step 8 - initializeApp(async_client1, app, getAuth(user_auth), processData, "🔐 authTask"); - - // Step 9 - app.getApp(Database); - - // Step 10 - Database.url("DATABASE_URL"); -} - -void loop() -{ - // Step 11 - app.loop(); - - // Step 12 - if (app.ready() && !onetimeTest) - { - onetimeTest = true; - - // Realtime Database set value. - // ============================ - - // Async call with callback function - Database.set(async_client1, "/examples/BareMinimum/data/set1", "abc", processData, "RealtimeDatabase_SetTask"); - - // Async call with AsyncResult for returning result. - Database.set(async_client1, "/examples/BareMinimum/data/set2", true, dbResult); - - // Sync call which waits until the result was received. - bool status = Database.set(async_client2, "/examples/BareMinimum/data/set3", 199.538639); - if (status) - Serial.println("Value set complete."); - else - Firebase.printf("Error, msg: %s, code: %d\n", async_client2.lastError().message().c_str(), async_client2.lastError().code()); - - // Realtime Database get value. - // ============================ - - // Async call with callback function - Database.get(async_client1, "/examples/BareMinimum/data/set1", processData, false, "RealtimeDatabase_GetTask"); - - // Async call with AsyncResult for returning result. - Database.get(async_client1, "/examples/BareMinimum/data/set2", dbResult, false); - - String value = Database.get(async_client2, "/examples/BareMinimum/data/set3"); - if (async_client2.lastError().code() == 0) - { - Serial.println("Value get complete."); - Serial.println(value); - } - else - Firebase.printf("Error, msg: %s, code: %d\n", async_client2.lastError().message().c_str(), async_client2.lastError().code()); - } - - // Step 13 - processData(dbResult); -} - -void processData(AsyncResult &aResult) -{ - // Exits when no result available when calling from the loop. - if (!aResult.isResult()) - return; - - if (aResult.isEvent()) - Firebase.printf("Event task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.eventLog().message().c_str(), aResult.eventLog().code()); - - if (aResult.isDebug()) - Firebase.printf("Debug task: %s, msg: %s\n", aResult.uid().c_str(), aResult.debug().c_str()); - - if (aResult.isError()) - Firebase.printf("Error task: %s, msg: %s, code: %d\n", aResult.uid().c_str(), aResult.error().message().c_str(), aResult.error().code()); - - if (aResult.available()) - Firebase.printf("task: %s, payload: %s\n", aResult.uid().c_str(), aResult.c_str()); -} diff --git a/examples/BareMinimum/RealtimeDatabase/RealtimeDatabase_Sync.ino b/examples/BareMinimum/RealtimeDatabase/RealtimeDatabase_Sync.ino new file mode 100644 index 00000000..118af25c --- /dev/null +++ b/examples/BareMinimum/RealtimeDatabase/RealtimeDatabase_Sync.ino @@ -0,0 +1,155 @@ +/** + * The bare minimum code example for using the Realtime Database service in a synchronous mode. + * + * The generally required steps are explained below. This example is best suited for simple, single-threaded applications. + * For multi-tasking or RTOS environments, please see the `RealTimeDatabase_Async` example. + * + * Step 1. Include the required Firebase defines and libraries. + * ================================================================= + * + * Step 2. Define the authentication configuration (identifier) object. + * ====================================================================== + * UserAuth (user authentication with email/password) is the basic authentication method for the Realtime Database, + * Firebase Storage, and Firestore services. It stores the email, password, and API key for the authentication process. + * + * Step 3. Define the authentication handler object. + * ================================================== + * The FirebaseApp object works as the authentication handler. + * It manages the authentication process and token refresh cycles. + * + * Step 4. Define the SSL client. + * ================================ + * The SSL client handles the server connection and data transfer. + * + * Step 5. Define the Async Client. + * ================================ + * The library's synchronous functions still require an AsyncClient object. + * + * Step 6. Define the class that provides the Firebase/Google Cloud service. + * ============================================================================ + * The Firebase/Google Cloud service classes provide the member functions for database operations. + * + * Step 7. Start the authentication process. + * ========================================== + * At this step, the authentication credentials will be used by `initializeApp` to begin + * the authentication process. + * + * Step 8. Bind the FirebaseApp (authentication handler) with your Firebase/Google Cloud service object. + * ========================================================================================================= + * + * Step 9. Set the Realtime Database URL (for the Realtime Database only). + * ======================================================================== + * + * Step 10. Maintain authentication in the loop. + * ============================================= + * `app.loop()` is required to manage the authentication token refresh cycle. + * + * Step 11. Check the authentication status before use. + * ==================================================== + * Before calling a Firebase service function, the `FirebaseApp::ready()` function should return true. + * + * Step 12. Call the Firebase service functions synchronously. + * =========================================================== + * In synchronous mode, the function call (e.g., `Database.set(...)`) will not return until the server has responded. + * This blocks the execution of your code, but simplifies the logic. + */ + +// Step 1 +#define ENABLE_USER_AUTH +#define ENABLE_DATABASE + +#include +#include +#include + +// Step 2 +UserAuth user_auth("WEB_API_KEY", "UESR_EMAIL", "USER_PASSWORD"); + +// Step 3 +FirebaseApp app; + +// Step 4 +// Use SSL client for sync tasks. +WiFiClientSecure ssl_client; + +// Step 5 +// Use AsyncClient for sync tasks. +using AsyncClient = AsyncClientClass; +AsyncClient async_client(ssl_client); + +// Step 6 +RealtimeDatabase Database; + +bool onetimeTest = false; + + +void setup() +{ + Serial.begin(115200); + + WiFi.begin("WIFI_AP", "WIFI_PASSWORD"); + + Serial.print("Connecting to Wi-Fi"); + while (WiFi.status() != WL_CONNECTED) + { + Serial.print("."); + delay(300); + } + Serial.println(); + Serial.print("Connected with IP: "); + Serial.println(WiFi.localIP()); + Serial.println(); + + // Skip certificate verification + ssl_client.setInsecure(); + + // Note: The setConnectionTimeout and setHandshakeTimeout functions are + // deprecated in modern ESP32 cores and may cause compiler errors. + // They are left here for compatibility with older core versions. + ssl_client.setConnectionTimeout(1000); + ssl_client.setHandshakeTimeout(5); + + // Step 7 + initializeApp(async_client, app, getAuth(user_auth), NULL, "authTask"); + + // Step 8 + app.getApp(Database); + + // Step 9 + Database.url("DATABASE_URL"); +} + +void loop() +{ + // Step 10 + app.loop(); + + // Step 11 + if (app.ready() && !onetimeTest) + { + onetimeTest = true; + + // Step 12 + // Realtime Database set value (Synchronous). + // =========================================== + // This is a blocking call. Your code will pause here until the + // server responds or the operation times out. + bool status = Database.set(async_client, "/examples/BareMinimum/data/set3", 199.538739); + if (status) + Serial.println("Value set complete."); + else + Firebase.printf("Error, msg: %s, code: %d\n", async_client.lastError().message().c_str(), async_client.lastError().code()); + + // Realtime Database get value (Synchronous). + // =========================================== + // This is a blocking call. + String value = Database.get(async_client, "/examples/BareMinimum/data/set3"); + if (async_client.lastError().code() == 0) + { + Serial.println("Value get complete."); + Serial.println(value); + } + else + Firebase.printf("Error, msg: %s, code: %d\n", async_client.lastError().message().c_str(), async_client.lastError().code()); + } +}