Firebase: Control ESP8266 NodeMCU GPIOs from Anywhere

In this guide, you’ll learn how to control the ESP8266 GPIOs from anywhere using Firebase. We’ll create nodes on the Firebase Realtime Database to save the current GPIO states. Whenever there’s a change in the database nodes, the ESP8266 updates its GPIOs accordingly. You can change the GPIOs states by writing on the database yourself, or you can create a web app to do that (check this tutorial).

Control ESP8266 NodeMCU GPIOs from Anywhere using Firebase

PART 2: Control ESP32/ESP8266 GPIOs from Anywhere (Firebase Web App)

We have a similar tutorial for the ESP32 board: Firebase: Control ESP32 GPIOs from Anywhere.

Other Firebase Tutorials with the ESP8266 that you might be interested in:

What is Firebase?

Firebase logo

Firebase is Google’s mobile application development platform that helps you build, improve, and grow your app. Firebase provides free services like hosting, authentication, and realtime database that allow you to build a fully-featured web app to control and monitor the ESP32 and ESP8266 boards that would be much more difficult and laborious to build and set up on your own.

Project Overview

The following diagram shows a high-level overview of the project we’ll build.

ESP8266 NodeMCU Firebase Control Outputs from Anywhere Project Overview
  1. The ESP8266 authenticates as a user with email and password to be able to access the database (that user must be added on the Firebase authentication methods);
  2. The database is protected using database rules. We’ll add the following rule: only authenticated users can access the database;
  3. The database has several nodes that save the ESP8266 GPIO states. As an example, we’ll control three GPIOs (12, 13, and 14). You can add or remove nodes to control more or less GPIOs.
  4. The ESP8266 will listen for changes on the GPIOs database nodes. Whenever there’s a change, it will update the GPIO states accordingly.
  5. You can change the GPIO states manually on the database using the Firebase console, or you can create a web page (accessible from anywhere) with buttons to control the GPIOs and show the current GPIO states (check PART 2).

These are the main steps to complete this project:

  1. Create a Firebase Project
  2. Set Authentication Methods
  3. Get Project API Key
  4. Set up the Realtime Database
  5. Set up Database Security Rules
  6. Organizing your Database Nodes
  7. ESP8266: Listening for Database Changes (control GPIOs)

Preparing Arduino IDE

For this tutorial, we’ll program the ESP8266 board using the Arduino core. So, make sure you have the ESP8266 add-on installed in your Arduino IDE:

If you want to program the ESP boards using VS Code with the PlatformIO extension, follow the next tutorial instead:

1) Create Firebase Project

1) Go to Firebase and sign in using a Google Account.

2) Click Get Started and then Add project to create a new project.

3) Give a name to your project, for example ESP Firebase Demo.

Create Firebase Project name

4) Disable the option Enable Google Analytics for this project as it is not needed and click Create project.

Set Up Firebase Project for ESP32 and ESP8266 Step 2

5) It will take a few seconds to set up your project. Then, click Continue when it’s ready.

6) You’ll be redirected to your Project console page.

2) Set Authentication Methods

To allow authentication with email and password, first, you need to set authentication methods for your app.

“Most apps need to know the identity of a user. In other words, it takes care of logging in and identifying the users (in this case, the ESP32 or ESP8266). Knowing a user’s identity allows an app to securely save user data in the cloud and provide the same personalized experience across all of the user’s devices.” To learn more about the authentication methods, you can read the documentation.

1) On the left sidebar, click on Authentication and then on Get started.

Firebase Project Authentication

2) Select the Option Email/Password.

Selecting Firebase Authentication with Email/Password

3) Enable that authentication method and click Save.

Enable Email/password authentication Firebase

4) The authentication with email and password should now be enabled.

Firebase Authentication with Email/Password enabled

5) Now, you need to add a user. On the Authentication tab, select the Users tab at the top. Then, click on Add User.

Firebase Authentication Add New User

6) Add an email address for the authorized user. It can be your google account email or any other email. You can also create an email for this specific project. Add a password that will allow you to sign in to your app and access the database. Don’t forget to save the password in a safe place because you’ll need it later. When you’re done, click Add user.

Firebase Authentication Add User with Email and Password

7) A new user was successfully created and added to the Users table.

Firebase Users Table

Notice that Firebase creates a unique UID for each registered user. The user UID allows us to identify the user and keep track of the user to provide or deny access to the project or the database. There’s also a column that registers the date of the last sign-in. At the moment, it is empty because we haven’t signed in with that user yet.

Copy the User UID because you’ll need it later.

3) Get Project API Key

To interface with your Firebase project using the ESP8266 boards, you need to get your project API key. Follow the next steps to get your project API key.

1) On the left sidebar, click on Project Settings.

Firebase Project Settings

2) Copy the Web API Key to a safe place because you’ll need it later.

Firebase Project Settings Web API Key

4) Set up the Realtime Database

Now, let’s create a realtime database and set up database rules for our project.

1) On the left sidebar, click on Realtime Database and then click on Create Database.

Firebase Project Create Realtime Database

2) Select your database location. It should be the closest to your location.

Set up realtime database firebase ESP32 ESP8266 Select Location

3) Set up security rules for your database. You can select Start in test mode. We’ll change the database rules in just a moment.

Set up realtime database firebase ESP32 ESP8266 Set Security Rules

4) Your database is now created. You need to copy and save the database URL—highlighted in the following image—because you’ll need it later in your ESP8266 code.

Firebase Project Database URL

5) Set up Database Security Rules

Now, let’s set up the database rules. On the Realtime Database tab, select the Rules tab at the top. Then, click on Edit rules, and add the following rules.

{
  "rules": {
    ".read": "auth.uid === 'REPLACE_WITH_YOUR_USER_UID'",
    ".write": "auth.uid === 'REPLACE_WITH_YOUR_USER_UID'"
  }
}

Insert the UID of the user you created previously. Then, click Publish.

These database rules determine that:

  • Only the user with that specific UID can read and write to the database (change the GPIO states).

Add More Users

To add more users, you can simply go to the Authentication tab and click Add user. Add an email and password for the new user, and finally click on Add user to create the user.

Firebase Add New User Email and Password Console

Copy the user UID of that new user and add it to the database rules, as follows:

{
  "rules": {
    ".read": "auth.uid === 'REPLACE_WITH_YOUR_USER_UID' || auth.uid === 'REPLACE_WITH_USER_UID2'",
    ".write": "auth.uid === 'REPLACE_WITH_YOUR_USER_UID' || auth.uid === 'REPLACE_WITH_USER_UID2'"
  }
}

For example. In my case, the UIDs of the users are: RjO3taAzMMXB82Xmir2LQ7XXXXXX and 9QdDc9as5mRXGAjEsQiUJkXXXXXX. So, the database rules will look as follows:

{
  "rules": {
    ".read": "auth.uid === 'RjO3taAzMMXB82Xmir2LQ7XXXXXX' || auth.uid === '9QdDc9as5mRXGAjEsQiUJkXXXXXX'",
    ".write": "auth.uid === 'RjO3taAzMMXB82Xmir2LQ7XXXXXX' || auth.uid === '9QdDc9as5mRXGAjEsQiUJkXXXXXX'"
  }
}

Finally, Publish your database rules.

To learn more about database rules, you can check the Firebase documentation.

6) Organizing Your Database Nodes

All the data stored in the Firebase Realtime Database is stored as JSON objects. So, you can think of the database as a cloud-based JSON tree. When you add data to the JSON tree, it becomes a node with an associated key in the existing JSON structure.

Not familiar with JSON? Read this quick guide.

The best way to organize your data will depend on your project features and how users access the data.

We want to control the ESP8266 GPIOs. We can organize the data in a way that makes it easy to add more GPIOs and boards later on. So, we can structure the database as follows:

  • board1
    • outputs:
      • digital:
        • 12: 0
        • 13: 0
        • 14: 0

In JSON format, here’s what it would look like:

{
  "board1": {
    "outputs": {
      "digital": {
        "12": 0,
        "13": 0,
        "14": 0
      }
    }
  }
}

Creating Database Nodes

Now let’s create the database nodes in our database. You can create the nodes manually by writing the nodes on the Firebase console, on the web app, or via the ESP8266. We’ll create them manually, so it is easier to follow the tutorial.

1) Click on the Realtime Database so that we start creating the nodes.

Firebase Realtime Database Menu

2) You can create the database nodes manually by using the (+) icons on the database. However, to prevent typos, we provide a JSON file that you can upload to create the same nodes as ours. Click the link below to download the JSON file.

3) Now, go back to your database on the Firebase console. Click on the three-dot icon and select Import JSON.

Firebase Realtime Database Import JSON

4) Select the JSON file that you’ve just downloaded.

5) Your database should look as shown below.

Firebase realtime database structure example

All the database nodes required for this project are created. You can proceed to the next section.

7) ESP8266: Listening for Database Changes (control GPIOs)

In this section, we’ll program the ESP8266 boards to do the following tasks:

  1. Authenticate as a user with email and password (the user you set up in this section);
  2. Listening for database changes on the GPIO nodes and changing their states accordingly.

Parts Required

For this project, you need the following parts:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Schematic Diagram

In this example, we’ll control three LEDs connected to GPIOs 12 (D6), 13 (D7), and 14 (D5). So, wire three LEDs to the ESP8266. You can follow the next schematic diagram.

ESP8266 connected to three LEDs Schematic diagram wiring circuit

You can use any other suitable ESP8266 GPIOs, but you also need to change the database nodes.

Installing the Firebase ESP Client Library

The Firebase ESP Client Library provides many examples of how to interface the ESP8266 with Firebase services. Check the library Github page here and consider supporting the author if the library is useful for your projects.

Install the Firebase-ESP-Client Library (Arduino IDE)

Follow this section if you’re using Arduino IDE.

Go to Sketch > Include Library > Manage Libraries, search for “Firebase ESP Client”. Select the Firebase Arduino Client Library for ESP8266 and ESP32.

Install Firebase Arduino Client Library for ESP8266 and ESP32 by Mobitz

Now, you’re all set to start programming the ESP8266 boards to interact with the database.

Install the Firebase-ESP-Client Library (VS Code)

Follow the next instructions if you’re using VS Code + PlatformIO.

Click on the PIO Home icon and select the Libraries tab. Search for “Firebase ESP Client“. Select the Firebase Arduino Client Library for ESP8266 and ESP32.

Install Firebase ESP Client Library VS Code

Then, click Add to Project and select the project you’re working on.

Add Firebase ESP Client Library to Project VS Code

Also, change the monitor speed to 115200 by adding the following line to the platformio.ini file of your project:

monitor_speed = 115200

Listening for Database Changes (GPIO states) — Code

Copy the following code to your Arduino IDE or to the main.cpp file if you’re using VS Code.

You need to insert the following in the code before uploading it to your board:

  • your network credentials
  • project API key
  • database URL
  • authorized user email and password
/*
  Rui Santos
  Complete project details at our blog.
    - ESP32: https://RandomNerdTutorials.com/firebase-control-esp32-gpios/
    - ESP8266: https://RandomNerdTutorials.com/firebase-control-esp8266-nodemcu-gpios/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
  Based in the RTDB Basic Example by Firebase-ESP-Client library by mobizt
  https://github.com/mobizt/Firebase-ESP-Client/blob/main/examples/RTDB/Basic/Basic.ino
*/

#include <Arduino.h>
#if defined(ESP32)
  #include <WiFi.h>
#elif defined(ESP8266)
  #include <ESP8266WiFi.h>
#endif
#include <Firebase_ESP_Client.h>

// Provide the token generation process info.
#include "addons/TokenHelper.h"
// Provide the RTDB payload printing info and other helper functions.
#include "addons/RTDBHelper.h"

// Insert your network credentials
#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"

// Insert Firebase project API Key
#define API_KEY "REPLACE_WITH_YOUR_PROJECT_API_KEY"

// Insert Authorized Username and Corresponding Password
#define USER_EMAIL "REPLACE_WITH_THE_USER_EMAIL"
#define USER_PASSWORD "REPLACE_WITH_THE_USER_PASSWORD"

// Insert RTDB URLefine the RTDB URL
#define DATABASE_URL "REPLACE_WITH_YOUR_DATABASE_URL"

// Define Firebase objects
FirebaseData stream;
FirebaseAuth auth;
FirebaseConfig config;

// Variables to save database paths
String listenerPath = "board1/outputs/digital/";

// Declare outputs
const int output1 = 12;
const int output2 = 13;
const int output3 = 14;

// Initialize WiFi
void initWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
  Serial.println();
}

// Callback function that runs on database changes
void streamCallback(FirebaseStream data){
  Serial.printf("stream path, %s\nevent path, %s\ndata type, %s\nevent type, %s\n\n",
                data.streamPath().c_str(),
                data.dataPath().c_str(),
                data.dataType().c_str(),
                data.eventType().c_str());
  printResult(data); //see addons/RTDBHelper.h
  Serial.println();

  // Get the path that triggered the function
  String streamPath = String(data.dataPath());

  // if the data returned is an integer, there was a change on the GPIO state on the following path /{gpio_number}
  if (data.dataTypeEnum() == fb_esp_rtdb_data_type_integer){
    String gpio = streamPath.substring(1);
    int state = data.intData();
    Serial.print("GPIO: ");
    Serial.println(gpio);
    Serial.print("STATE: ");
    Serial.println(state);
    digitalWrite(gpio.toInt(), state);
  }

  /* When it first runs, it is triggered on the root (/) path and returns a JSON with all keys
  and values of that path. So, we can get all values from the database and updated the GPIO states*/
  if (data.dataTypeEnum() == fb_esp_rtdb_data_type_json){
    FirebaseJson json = data.to<FirebaseJson>();

    // To iterate all values in Json object
    size_t count = json.iteratorBegin();
    Serial.println("\n---------");
    for (size_t i = 0; i < count; i++){
        FirebaseJson::IteratorValue value = json.valueAt(i);
        int gpio = value.key.toInt();
        int state = value.value.toInt();
        Serial.print("STATE: ");
        Serial.println(state);
        Serial.print("GPIO:");
        Serial.println(gpio);
        digitalWrite(gpio, state);
        Serial.printf("Name: %s, Value: %s, Type: %s\n", value.key.c_str(), value.value.c_str(), value.type == FirebaseJson::JSON_OBJECT ? "object" : "array");
    }
    Serial.println();
    json.iteratorEnd(); // required for free the used memory in iteration (node data collection)
  }
  
  //This is the size of stream payload received (current and max value)
  //Max payload size is the payload size under the stream path since the stream connected
  //and read once and will not update until stream reconnection takes place.
  //This max value will be zero as no payload received in case of ESP8266 which
  //BearSSL reserved Rx buffer size is less than the actual stream payload.
  Serial.printf("Received stream payload size: %d (Max. %d)\n\n", data.payloadLength(), data.maxPayloadLength());
}

void streamTimeoutCallback(bool timeout){
  if (timeout)
    Serial.println("stream timeout, resuming...\n");
  if (!stream.httpConnected())
    Serial.printf("error code: %d, reason: %s\n\n", stream.httpCode(), stream.errorReason().c_str());
}

void setup(){
  Serial.begin(115200);
  initWiFi();

  // Initialize Outputs
  pinMode(output1, OUTPUT);
  pinMode(output2, OUTPUT);
  pinMode(output3, OUTPUT);
  
  // Assign the api key (required)
  config.api_key = API_KEY;

  // Assign the user sign in credentials
  auth.user.email = USER_EMAIL;
  auth.user.password = USER_PASSWORD;

  // Assign the RTDB URL (required)
  config.database_url = DATABASE_URL;

  Firebase.reconnectWiFi(true);

  // Assign the callback function for the long running token generation task */
  config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h

  // Assign the maximum retry of token generation
  config.max_token_generation_retry = 5;

  // Initialize the library with the Firebase authen and config
  Firebase.begin(&config, &auth);

  // Streaming (whenever data changes on a path)
  // Begin stream on a database path --> board1/outputs/digital
  if (!Firebase.RTDB.beginStream(&stream, listenerPath.c_str()))
    Serial.printf("stream begin error, %s\n\n", stream.errorReason().c_str());

  // Assign a calback function to run when it detects changes on the database
  Firebase.RTDB.setStreamCallback(&stream, streamCallback, streamTimeoutCallback);

  delay(2000);
}

void loop(){
  if (Firebase.isTokenExpired()){
    Firebase.refreshToken(&config);
    Serial.println("Refresh token");
  }
}

View raw code

How the Code Works

Continue reading to learn how the code works or skip to the demonstration section.

Include Libraries

First, include the required libraries. This code is also compatible with the ESP8266.

#include <Arduino.h>
#if defined(ESP32)
  #include <WiFi.h>
#elif defined(ESP8266)
  #include <ESP8266WiFi.h>
#endif
#include <Firebase_ESP_Client.h>

// Provide the token generation process info.
#include "addons/TokenHelper.h"
// Provide the RTDB payload printing info and other helper functions.
#include "addons/RTDBHelper.h"

Network Credentials

Include your network credentials in the following lines so that your boards can connect to the internet using your local network.

// Insert your network credentials
#define WIFI_SSID "REPLACE_WITH_YOUR_SSID"
#define WIFI_PASSWORD "REPLACE_WITH_YOUR_PASSWORD"

Firebase Project API Key, Firebase User, and Database URL

Insert your Firebase project API key—the one you’ve gotten in this section.

#define API_KEY "REPLACE_WITH_YOUR_PROJECT_API_KEY"

Insert the authorized email and the corresponding password—these are the details of the user you’ve added in this section.

// Insert Authorized Email and Corresponding Password
#define USER_EMAIL "REPLACE_WITH_THE_USER_EMAIL"
#define USER_PASSWORD "REPLACE_WITH_THE_USER_PASSWORD"

Insert your database URL in the following line:

// Insert RTDB URLefine the RTDB URL
#define DATABASE_URL "REPLACE_WITH_YOUR_DATABASE_URL"

Firebase Objects and Other Variables

First, you need to create a FirebaseData object (we called it stream) to handle the data when there’s a change on a specific database path.

FirebaseData stream;

The next line defines a FirebaseAuth object needed for authentication.

FirebaseAuth auth;

Finally, the following line defines a FirebaseConfig object required for configuration data.

FirebaseConfig config;

Database Path

Then, create a variable that saves the database path where we’ll be listening for changes. Taking into account the database structure we created previously, the listener database path should be as follows:

String listenerPath = "board1/outputs/digital/";

If you want to add more boards, then you just need to change the listener path accordingly.

Create variables for the outputs you’ll control. In our case, we’re controlling GPIOs 12, 13, and 14. You can control any other ESP8266 GPIOs (you’ll also need to change the database nodes):

const int output1 = 12;
const int output2 = 13;
const int output3 = 14;

initWifi()

The iniWifi() function connects the ESP8266 to your local network. We’ll call it later in the setup() to initialize Wi-Fi.

// Initialize WiFi
void initWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to WiFi ..");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    delay(1000);
  }
  Serial.println(WiFi.localIP());
  Serial.println();
}

setup()

Let’s now jump to the setup(). We’ll take a look at the streamCallback() function later.

Initialize the Serial Monitor:

Serial.begin(115200);

Call the initiWiFi() function we created previously, to connect your board to your local network.

initWiFi();

Initialize the GPIOs as outputs.

// Initialize Outputs
pinMode(output1, OUTPUT);
pinMode(output2, OUTPUT);
pinMode(output3, OUTPUT);

Assign the API key to the Firebase configuration.

config.api_key = API_KEY;

The following lines assign the email and password to the Firebase authentication object.

auth.user.email = USER_EMAIL;
auth.user.password = USER_PASSWORD;

Assign the database URL to the Firebase configuration object.

config.database_url = DATABASE_URL;

Add the following to the configuration object.

// Assign the callback function for the long running token generation task
config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h

// Assign the maximum retry of token generation
config.max_token_generation_retry = 5;

Initialize the Firebase library (authenticate) with the configuration and authentication settings we defined earlier.

// Initialize the library with the Firebase authen and config
Firebase.begin(&config, &auth);

Stream Database – Listening for Changes

Listening for changes on the database works with callback functions. This means, that when a change is detected on the database, a callback function will run.

The following line of code starts a stream to listen for changes on the listenerPath.

if (!Firebase.RTDB.beginStream(&stream, listenerPath.c_str()))

Then, set a callback function to be triggered whenever there’s a change on the listenerPath—the streamCallback function.

Firebase.RTDB.setStreamCallback(&stream, streamCallback, streamTimeoutCallback);

streamCallback() function

Let’s now take a look at the streamCallback function created previously.

When the streamCallback() function is triggered, an object called data of type FirebaseStream is passed automatically as an argument to that function. From that object, we can get the stream path, the data path (the full database path where the change occurred, including the value of the lowest child), the data type of that value, and the event type that triggered the stream.

void streamCallback(FirebaseStream data){
  Serial.printf("stream path, %s\nevent path, %s\ndata type, %s\nevent type, %s\n\n",
                data.streamPath().c_str(),
                data.dataPath().c_str(),
                data.dataType().c_str(),
                data.eventType().c_str());
  printResult(data); //see addons/RTDBHelper.h
  Serial.println();

  // Get the path that triggered the function
  String streamPath = String(data.dataPath());

Then, from the obtained information, the ESP can run certain tasks, like updating the GPIO states.

When you change the value of the GPIO states on the database, the data returned by that event is an integer (in this case, 0 or 1). So, first, we check if the response is an integer:

if (data.dataTypeEnum() == fb_esp_rtdb_data_type_integer){

If it is, the event path corresponds to the GPIO node path, for example /12. From that path, we can get the GPIO number we want to change, we just need to cut the “/” from the string.

String gpio = streamPath.substring(1);

To better understand this, you can take a look at the following screenshot. It shows what you get when there’s a change in the GPIOs states.

Firebase Streaming Database ESP Detecting Changes Serial Monitor

We can get the value of the returned data as follows (knowing beforehand that it is an integer):

int state = data.intData();

Then, we can simply call the digitalWrite() function and pass as arguments the GPIO number and the state to keep the ESP8266 output states updated.

digitalWrite(gpio.toInt(), state);

When the ESP first connects to the database, it is triggered on the root(/) path and returns a JSON object with all child nodes. So, we can get all values from the database and update the ESP8266 GPIOs when it first runs. This is also useful because if the ESP8266 resets, it will always receive this JSON object first, and will be able to update all GPIOs.

Streaming Database First Run

As you can see from the previous screenshot, the JSON object it receives looks as follows (it might be different depending on the GPIO states):

{
  "12": 0,
  "13": 0,
  "14": 0
}

When this happens, the returned data is of type JSON. So, we can get it with the following if statement:

if (data.dataTypeEnum() == fb_esp_rtdb_data_type_json){

We can convert the returned data to a FirebaseJSON object:

FirebaseJson json = data.to<FirebaseJson>();

Then, we can iterate through the whole JSON object and get the keys (GPIOs) and corresponding values (GPIO states). In each iteration, we save the GPIO on the gpio variable and its corresponding state on the state variable. Then, we call the digitalWrite() function to update its state.

// To iterate all values in Json object
size_t count = json.iteratorBegin();
Serial.println("\n---------");
for (size_t i = 0; i < count; i++){
    FirebaseJson::IteratorValue value = json.valueAt(i);
    int gpio = value.key.toInt();
    int state = value.value.toInt();
    Serial.print("STATE: ");
    Serial.println(state);
    Serial.print("GPIO:");
    Serial.println(gpio);
    digitalWrite(gpio, state);
    Serial.printf("Name: %s, Value: %s, Type: %s\n", value.key.c_str(), value.value.c_str(), value.type == FirebaseJson::JSON_OBJECT ? "object" : "array");
}
Serial.println();
json.iteratorEnd(); // required for free the used memory in iteration (node data collection)

This runs through all keys and values allowing us to update all GPIOs.

Because all our code works with callback functions, we don’t need to place anything on the loop() besides the lines to refresh the Firebase token.

void loop(){
  if (Firebase.isTokenExpired()){
    Firebase.refreshToken(&config);
    Serial.println("Refresh token");
  }
}

If the ESP8266 needs to perform other tasks, you can add them to the loop(). In our case, this example only listens for database changes.

Demonstration

After inserting all required credentials, upload the code to your board.

After uploading, open the Serial Monitor at a baud rate of 115200 and reset the board. You should get something as shown below.

Streaming Database First Run

As you can see, when the ESP first runs, it gets a JSON object with all GPIO states.

{
  "12": 0,
  "13": 0,
  "14": 0
}

Then, go to the Firebase Realtime Database on the Firebase console. Manually change the GPIO states (either 0 or 1). After inserting a new value, press Enter.

Firebase Realtime Database Saving ESP GPIO States

Right after, you’ll see on the Serial Monitor that the ESP8266 detected the changes.

Firebase Streaming Database ESP Detecting Changes Serial Monitor

And it will update the GPIO states and light up the LEDs almost instantaneously.

Control ESP8266 Outputs from Anywhere

Then, if you reset your board (press the RST button or remove and apply power again), when it restarts, it will get the latest GPIO states from the database and updates them right away.

Taking it Further – Add More Boards

You can take this project further and add more boards. To do that, create new database nodes for a second board. You can add ESP32 or ESP8266 boards.

You can download the following JSON file and import it to your database, and it will create nodes for two boards:

After uploading the JSON file, the database will look as follows:

Firebase Realtime Database Save GPIO Sates Multiple Boards

Now, you can upload the same code to the new board (it is compatible with the ESP32 and ESP8266). But don’t forget to change the listening path. It should be:

String listenerPath = "board2/outputs/digital/";

Now, you can control both boards by changing the GPIO states on the database.

Control ESP32 and ESP8266 Outputs from Anywhere Multiple Boards

In Part 2, we’ll create a Firebase web app so that you have a nice interface to control your GPIOs from anywhere without having to use the Firebase console and change the database manually:

Wrapping Up

In this tutorial, you learned how to use the Firebase Realtime Database to save the ESP GPIO states. You also learned how to program the ESP8266 to listen for database changes. Whenever a change is detected, we updated the corresponding GPIO states. You can change the code so that the ESP listens for any other data saved on the database, not only GPIO states. Because you can access the Firebase Realtime Database from anywhere, you can control your boards from anywhere too. This is great for IoT projects.

In Part 2, we’ll create a web app to control your GPIOs from anywhere, without the need to login manually on the Firebase console:

If you like Firebase projects, please take a look at our new eBook. We’re sure you’ll like it:

Learn more about the ESP8266 with our resources:

Thanks for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

42 thoughts on “Firebase: Control ESP8266 NodeMCU GPIOs from Anywhere”

  1. Great, I did something experimental like that in Google Sheet, long time ago, mainly because it was easy to change values. Firebase might be a better option. Will see if I can make an app for it (but still so many projects waiting)

    Reply
  2. Hi, thanks for the excellent tutorial. I’ve successfully implemented the GPIO project and also the data write/read projects, but when I try to combine both together on the ESP8266, the code compiles OK and the data read/write runs ok in the main program loop but the GPIO routines to listen for database changes (implemented in the set up routines) don’t seem to operate. If I disable the main program loop, the GPIO routines work OK. Is there a conflict between listening for database changes and actively reading and writing to the same database?

    Any thoughts on how can I change the config to allow both would be most welcome!

    Reply
    • Hi.
      There should be no issues combining both features: streaming database and sending sensor data.
      It should be something not implemented properly in your code.
      When you say it doesn’t operate, what happens exactly?
      Regards,
      Sara

      Reply
      • Hi Sarah,

        It seems to be that if I try to run a database write (setInt) routine from within the main program loop, this prevents the database listening routine from operating. I can run simple serial.print commands from within the main loop and the database listening function still operates OK, its just when you add in the database read/write to the main program loop that the streamCallback function stops working (which is not in the main program loop).

        Reply
  3. Hi Sarah,

    It seems to be that if I try to run a database write (setInt) routine from within the main program loop, this prevents the database listening routine from operating. I can run simple serial.print commands from within the main loop and the database listening function still operates OK, its just when you add in the database read/write to the main program loop that the streamCallback function stops working (which is not in the main program loop).

    Reply
    • Hi again.
      There must be something wrong with your code.
      I’ve built a similar project before and it worked as expected… it’s featured in our “Firebase Web App with ESP32 and ESP8266” eBook.
      Regards,
      Sara

      Reply
      • Hi Sara,

        I bought the eBook on ESP – Firebase Web Apps and on reading the book I realised what my error was. I had only declared one FirebaseData object, “stream” for the callback function and not one “fbdo” for the database write function, hence that function wasn’t working. Having declared both objects, the listening and writing functions now work as intended.

        Regards,
        Peter

        Reply
  4. This is a great project and together with the app it sure must have been a lot of effort.
    anyway, it works well, but I did experience some odd behaviour. Just wondering if other people experienced it as well.
    As said, everything works well…for a couple of hours. Then the ESP8266 loses its connection with firebase. It is still connected to internet (as I can see in my router), but it just will not respond to status change from firebase anymore.If I check firebase, that still will respond to the app…I can see the status change right in front of me, but the ESP8266 does not respond anymore. As expected a reset will bring it all back on line. Ofcourse I can solve it by a software reboot say every 3 hrs, but that’s not really elegant. While I go dive deeper into it, it would be helpful to know if anybody else experienced sumilar behaviour

    Reply
    • Hi.
      Yes.
      It seems an issue with the token update. I’ll have to check what needs to be done on the code to prevent that issue.
      Regards,
      Sara

      Reply
      • Hi Sara,
        From monitoring on the serial port, it seems that every 57 or 58 minutes the Firebase token is reissued/ refreshed and this causes the ESP8266 to crash and reboot due to ‘OOM’ error (Out of Memory)? Does the refreshed token or the updated database stream contain so much data that it uses up all the available RAM when trying to process it? This is the error report that I get on the serial port.

        PATH: /Heathmount/Battery
        PATH: /Heathmount/Runtime
        12v DC Supply Volts = 1.69
        Minutes run = 57

        FAILED
        REASON: send request failed
        PATH: /Heathmount/Runtime
        12v DC Supply Volts = 1.78
        Minutes run = 58

        Token info: type = id token, status = on refreshing

        User exception (panic/abort/assert)
        ————— CUT HERE FOR EXCEPTION DECODER —————

        Unhandled C++ exception: OOM

        stack>>>
        Contents
        <<<stack<<<

        last failed alloc call: 40232473(1496)

        ————— CUT HERE FOR EXCEPTION DECODER —————

        last failed alloc caller: 0x40232473

        ets Jan 8 2013,rst cause:2, boot mode:(3,6)

        load 0x4010f000, len 3460, room 16
        tail 4
        chksum 0xcc
        load 0x3fff20b8, len 40, room 4
        tail 4
        chksum 0xc9
        csum 0xc9
        v000cd4e0
        ~ld
        Connecting to WiFi ..
        ….Connected to Wi-Fi sucessfully.

        I also noticed this comment on the instructions for the ESP Firebase Library
        // Optional, set the size of HTTP response buffer
        // Prevent out of memory for large payload but data may be truncated and can’t determine its type.
        fbdo.setResponseSize(1024); // minimum size is 1024 bytes
        I wondered if this is perhaps causing the out of memory restarts? You have set this value at 4096 but perhaps it needs to be adjusted?

        Hope this might be of some interest/value to you.

        Peter

        Reply
        • Interesting, I did not experience that. I just lose the channel to Firebase (not the internet connection) as it seems every half hour or so. There is no crash/reboot.
          i wonder though that if I would not lose connection every half hour, maybe I would experience your problem.
          What I have seen though from snooping around the internet is that the re-issuing of tokens can cause problems. TBH I have seen such reports date back to 2016…one would think the issues would be solved by now.
          on the other hand, lots of people do NOT seem to have problems with firebase/ESP

          Reply
          • Hi Ed,

            It’s interesting, initially I was experiencing exactly what you described – just a loss of communication with Firebase after a couple of hours of operation. Serial reporting thus:

            Counter: 237 (minutes)
            stream path, /********/Line/
            event path, /12
            data type,
            event type, auth_revoked
            Received stream payload size: 57 (Max. 68)

            After which authority revocation, the streamcallback function no longer operated, but everything else did.

            However, after some more tinkering with the code for various project enhancements, I now have the situation where after every 59 mins, without fail, Firebase appears to re-issue the authority token which now causes the NodeMCU to crash completely due to OOM (out of memory error?). My code uses 80% of Flash memory – is that too much?

            Hopefully Sara can work out what is going on with the Firebase Tokens, but perhaps the solution needs to be in the TokenHelper.h library, rather than in the project code.

            Peter

          • Hi Ed,

            Thinking about this, are you only listening to the Firebase database or are you also writing data to it? Initially I was only using the streamCallback function to listen with the FirebaseData stream object and that’s when I was being disconnected every few hours. When I added in the writing function to my code, using the FirebaseData fbdo object, that’s when the NodeMCU started crashing every hour, so I think this is also perhaps significant for this issue.

            Peter

          • Thanks Peter,Obviously this is an issue that has received some attention on internet. Regarding your question ‘do I write data to Firebase as well’, no, nothing other than the state changes that are coming from the webapp, but not from the ESP. I understand that sending data (say an analog value) together with receiving data (say the required state of a pin) in fact requires two channels. But I am just using the project as is presented by Sara.
            before I discovered writing data required a separate channel, I considered writing a dummy value say every 20 minutes to keep the cahnnel open…but that will not be a solution.
            I will look into the fingerprint as some people claim the solution might be there.
            regards
            Ed

          • Hi, I solved this problem by putting this code in loop, I think maybe there’s a better solution but that works for now, it is because the tokan is expired and nothing is called after than to refresh it
            if(Firebase.isTokenExpired())
            Firebase.refreshToken(&FirebaseConfigObj);

          • Sirito, Which firebase library version are you using? My 4.0.4 version does not know the refresh.Token instruction
            ‘class Firebase_ESP_Client’ has no member named ‘refreshToken’

          • Sirito, never mind, i was two versions behind. Updated and it compiles fine. But I think it should be:
            if(Firebase.isTokenExpired())

            Firebase.refreshToken(&config);
            if you are using Sarah’s code

  5. Hi Sara,

    I also added some code from the ESP Firebase library to periodically swap users on firebase, just to see what happened. Every time the user swap is attempted, the ESP8266 crashes as before, presumably when the new user token is issued. This time the error is reported as:

    User exception (panic/abort/assert)
    ————— CUT HERE FOR EXCEPTION DECODER —————

    Panic core_esp8266_main.cpp:137 __yield

    stack>>>
    <<<stack<<<

    ————— CUT HERE FOR EXCEPTION DECODER —————

    ets Jan 8 2013,rst cause:2, boot mode:(3,6)

    load 0x4010f000, len 3460, room 16
    tail 4
    chksum 0xcc
    load 0x3fff20b8, len 40, room 4
    tail 4
    chksum 0xc9
    csum 0xc9
    v000cd6e0
    @cp:B0
    ld
    Connecting to WiFi ..
    ….Connected to Wi-Fi sucessfully.

    Quite a puzzle?

    Peter

    Reply
  6. Great tutorial!
    I am using this code to upload some sensor data when the state of a GPIO is changed as per the tutorial. The sensor connects to A0 on my esp8266. However, using analogRead(A0) in my main loop disconnects the stream everytime. Interestingly, using analogRead in the streamCallback function does not give any error. Anyway to resolve this ?

    Reply
  7. void loop(){
    if (Firebase.isTokenExpired()){
    Firebase.refreshToken(&config);
    Serial.println(“Refresh token”);
    }
    }
    I’m getting an error message ‘class Firebase_ESP_CLIENT’ has no member named ‘refreshToken’

    Reply
  8. hai,
    I tried the sketch but an erroroccured
    stream begin error, sent request failed

    stream time out, resuming
    error code -3 reason not connected

    what’s the reason?

    Reply
    • Hi.
      Double-check your internet connection, double-check you’re using the right credentials, API key, database URL, and that you’ve enabled the proper database rules. You can’t skip any of the steps of the tutorial, otherwise, it won’t work properly.
      Regards,
      Sara

      Reply
  9. Dear Sara Santos,

    I just wanted to reach out and thank you for creating such a useful tutorial on Firebase listeners. I was struggling with an issue in my project, and your tutorial helped me solve it quickly and easily. Your clear explanations and examples were incredibly helpful.

    I did have a follow-up question, though. I was wondering if you could provide some guidance on how to monitor data changes of only a few KEYS, rather than the entire path in a real-time database(in above mentioned code you are trying to add a listener on all nodes). I think this would be a great addition to your tutorial and would help others who may be facing similar challenges.

    Thank you again for all your help, and I hope to see more tutorials from you in the future

    Reply
    • Hi.
      You just need to add the complete path if you just want to listen to one key.
      For example, if you just want to listen to changes on GPIO 12, you use the path as follows:
      String listenerPath = “board1/outputs/digital/12”;

      I hope this helps.
      Regards,
      Sara

      Reply
  10. Dear Sara,

    Thank you for your prompt response. I successfully tried listening to changes on one key. However, I would like to know how to listen to multiple keys. Specifically, if I have 12 keys in RTDB but only want to listen to changes on 5 of them, could you please assist me with this

    Regards,
    Furqan

    Reply
  11. hi sara:
    this code not working in vs code it gives error
    void streamCallback(FirebaseStream data){
    Serial.printf(“stream path, %s\nevent path, %s\ndata type, %s\nevent type, %s\n\n”,
    data.streamPath().c_str(),
    data.dataPath().c_str(),
    data.dataType().c_str(),
    data.eventType().c_str());
    FirebaseStream” has no member “streamPath”C/C++(135)

    Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.