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

In this guide, you’ll create a Firebase Web App to control the ESP32 or ESP8266 GPIOs from anywhere. The access to the web app is protected with authentication using email and password. The GPIO states are saved on the Firebase Realtime Database. The web app writes to the database to change the GPIO states and the ESP boards are listening for database changes to update the GPIO states accordingly.

Control ESP32 or ESP8266 NodeMCU GPIOs from Anywhere Firebase Web App

This article is Part 2 of a previous tutorial. You need to complete one of the following tutorials before proceeding:

Project Overview

In this tutorial (Part 2), you’ll create a web app to control the ESP32 or ESP8266 GPIOs from anywhere. In a previous tutorial, you learned how to set the ESP32 or ESP8266 to listen to database changes and update its GPIOs accordingly. You changed the GPIO states manually on the Realtime Database using the Firebase console. Now, you’ll create your own web app, hosted on Firebase, to control your boards from anywhere.

The following diagram shows a high-level overview of the project we’ll build—programming the ESP32/ESP8266 and setting up the Firebase Project was done in Part 1:

Firebase Web App control ESP32 ESP8266 Outputs from Anywhere
  • Firebase hosts your web app over a global CDN using Firebase Hosting and provides an SSL certificate. You can access your web app from anywhere using the Firebase-generated domain name.
  • When you first access the web app, you need to authenticate with an authorized email address and password. You already set up that user and the authentication method in Part 1.
  • After authentication, you can access a web app page that shows several buttons to change the GPIO states on the database.
  • The ESP32 or ESP8266 is listening to database changes. When you click on the buttons, the GPIO states change on the database, and the ESP updates the states accordingly.
  • The web app also shows what’s the current state of the GPIOs.
  • As an example, we’ll control three GPIOs (12, 13, and 14). As mentioned in the previous tutorial, you can add/remove more GPIOs and boards or control other GPIOs.
  • Once you’re logged in, you can logout any time. The next time you’ll access the app you’ll need to login again.

The following screenshot shows what the web page looks like on a computer web browser.

Control ESP GPIOs from Anywhere Web App

Prerequisites

Before start creating the Firebase Web App, you need to check the following prerequisites.

Creating a Firebase Project

You should have followed the one the following tutorials first:

The ESP32/ESP8266 must be running the code provided in that tutorial. The realtime database and authentication must be set up also as shown in the tutorial.

Install Required Software

Before getting started you need to install the required software to create the Firebase Web App. Here’s a list of the software you need to install (click on the links for instructions):


1) Add an App to Your Firebase Project

1) Go to your Firebase project Console and add an app to your project by clicking on the +Add app button.

Firebase Add App to Project

2) Select the web app icon.

Firebase Add Web App to Project

3) Give your app a name. Then, check the box next to âˆš Also set up Firebase Hosting for this App. Click Register app.

Firebase Add Web App to Project Hosting

4) Then, copy the firebaseConfig object and save it because you’ll need it later.

firebaseConfig object configuration copy save

After this, you can also access the firebaseConfig object if you go to your Project settings in your Firebase console.

5) Click Next on the proceeding steps, and finally on Continue to console.


2) Setting Up a Firebase Web App Project (VS Code)

Follow the next steps to create a Firebase Web App Project using VS Code.

1) Creating a Project Folder

1) Create a folder on your computer where you want to save your Firebase project—for example, Firebase-Project on the Desktop.

2) Open VS Code. Go to File > Open Folder… and select the folder you’ve just created.

3) Go to Terminal > New Terminal. A new Terminal window should open on your project path.

Install Firebase Tools 2

2) Firebase Login

4) On the previous Terminal window, type the following:

firebase login

5) You’ll be asked to collect CLI usage and error reporting information. Enter “n” and press Enter to deny.

Login Firebase VS Code Terminal Window

Note: If you are already logged in, it will show a message saying: “Already logged in as [email protected]”.

6) After this, it will pop up a new window on your browser to login into your firebase account.

Login Firebase Account

7) Allow Firebase CLI to access your google account.

Login Firebase Account allow Firebase CLI

8) After this, Firebase CLI login should be successful. You can close the browser window.

Login Firebase Account allow Firebase CLI Login Successful

3) Initializing Web App Firebase Project

9) After successfully login in, run the following command to start a Firebase project directory in the current folder.

firebase init

10) You’ll be asked if you want to initialize a Firebase project in the current directory. Enter Y and hit Enter.

Login Firebase Account allow Firebase CLI firebase init

11) Then, use up and down arrows and the Space key to select the options. Select the following options:

  • Realtime Database: Configure security rules file for Realtime Database and (optionally) provision default instance.
  • Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys

The selected options will show up with a green asterisk. Then, hit Enter.

Login Firebase Account allow Firebase CLI configure directory

12) Select the option “Use an existing project”—it should be highlighted in blue—then, hit Enter.

Firebase Project Setup VS Code

13) After that, select the Firebase project for this directory—it should be the project created in Part 1. In my case, it is called esp-firebase-demo. Then hit Enter.

Firebase Project VS Code create project

14) Press Enter on the following question to select the default database security rules file: “What file should be used for Realtime Database Security Rules?

15) Then, select the hosting options as shown below:

  • What do you want to use as your public directory? Hit Enter to select public.
  • Configure as a single-page app (rewrite urls to /index.html)? No
  • Set up automatic builds and deploys with GitHub? No
Firebase initialization complete

16) The Firebase project should now be initialized successfully. Notice that VS code created some essential files under your project folder.

Firebase Project Files Created successfully

The index.html file contains some HTML text to build a web page. For now, leave the default HTML text. The idea is to replace that with your own HTML text to build a custom web page for your needs. We’ll do that later in this tutorial.

17) To check if everything went as expected, run the following command on the VS Code Terminal window.

firebase deploy
Firebase App First Deploy Testing

You should get a Deploy complete! message and an URL to the Project Console and the Hosting URL.

18) Copy the hosting URL and paste it into a web browser window. You should see the following web page. You can access that web page from anywhere in the world.

Firebase test page hosting setup complete

The web page you’ve seen previously is built with the HTML file placed in the public folder of your Firebase project. By changing the content of that file, you can create your own web app. That’s what we’re going to do in the next section.


3) Creating Firebase Web App

Now that you’ve created a Firebase project app successfully on VS Code, follow the next steps to customize the app to display the sensor readings on a login-protected web page.

index.html

Copy the following to your index.html file. This HTML file creates a simple web page with ON and OFF buttons to control GPIOs 12, 13, and 14.

If you aren’t authenticated, it shows a login form. When you authenticate with an authorized user email and corresponding password, it shows the user interface with the buttons.

<!DOCTYPE html>
<!-- Complete Project Details at: https://RandomNerdTutorials.com/ -->
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ESP IoT Firebase App</title>

    <!-- update the version number as needed -->
    <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js"></script>

    <!-- include only the Firebase features as you need -->
    <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js"></script>
    <script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-database.js"></script>

    <script>
      // REPLACE WITH YOUR web app's Firebase configuration
      const firebaseConfig = {
        apiKey: "",
        authDomain: "",
        databaseURL: "",
        projectId: "",
        storageBucket: "",
        messagingSenderId: "",
        appId: ""
      };

      // Initialize firebase
      firebase.initializeApp(firebaseConfig);

      // Make auth and database references
      const auth = firebase.auth();
      const db = firebase.database();

    </script>
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
    <link rel="icon" type="image/png" href="favicon.png">
    <link rel="stylesheet" type="text/css" href="style.css">
</head>

<body>
  <!--TOP BAR-->
  <div class="topnav">
    <h1>ESP GPIO Control <i class="fas fa-lightbulb"></i></h1>
  </div>

  <!--AUTHENTICATION BAR (USER DETAILS/LOGOUT BUTTON)-->
  <div id="authentication-bar" style="display: none;">
    <p><span id="authentication-status">User logged in</span>
       <span id="user-details">USEREMAIL</span>
       <a href="/" id="logout-link">(logout)</a>
    </p>
  </div>

  <!--LOGIN FORM-->
  <form id="login-form" style="display: none;">
    <div class="form-elements-container">
      <label for="input-email"><b>Email</b></label>
      <input type="text" placeholder="Enter Username" id="input-email" required>

      <label for="input-password"><b>Password</b></label>
      <input type="password" placeholder="Enter Password" id="input-password" required>

      <button type="submit" id="login-button">Login</button>
      <p id="error-message" style="color:red;"></p>
    </div>
  </form>

  <!--CONTENT (SENSOR READINGS)-->
  <div class="content-sign-in" id="content-sign-in" style="display: none;">
    <div class="card-grid">
      <!--CARD FOR GPIO 12-->
      <div class="card">
        <p class="card-title"><i class="fas fa-lightbulb"></i> GPIO 12</p>
        <p>
          <button class="button-on" id="btn1On">ON</button>
          <button class="button-off" id="btn1Off">OFF</button>
        </p>
        <p class="state">State:<span id="state1"></span></p>
      </div>

      <!--CARD FOR GPIO 13-->
      <div class="card">
        <p class="card-title"><i class="fas fa-lightbulb"></i> GPIO 13</p>
        <p>
          <button class="button-on" id="btn2On">ON</button>
          <button class="button-off" id="btn2Off">OFF</button>
        </p>
        <p class="state">State:<span id="state2"></span></p>
      </div>

      <!--CARD FOR GPIO 14-->
      <div class="card">
        <p class="card-title"><i class="fas fa-lightbulb"></i> GPIO 14</p>
        <p>
          <button class="button-on" id="btn3On">ON</button>
          <button class="button-off" id="btn3Off">OFF</button>
        </p>
        <p class="state">State:<span id="state3"></span></p>
      </div>
    </div>
  </div>
    <script src="scripts/auth.js"></script>
    <script src="scripts/index.js"></script>
  </body>
</html>

View raw code

You need to modify the code with your own firebaseConfig object—the one you’ve got in this step.

How it Works

Let’s take a quick look at the HTML file, or skip to the next section.

In the <head> of the HTML file, we must add all the required metadata.

The title of the web page is ESP Firebase App, but you can change it in the following line.

<title>ESP Firebase App</title>

You must add the following line to be able to use Firebase with your app.

<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>

You must also add any Firebase products you want to use. In this example, we’re using the Realtime Database and Authentication.

<script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.8.1/firebase-database.js"></script>

Then, replace the firebaseConfig object with the one you’ve gotten from this step.

const firebaseConfig = {
  apiKey: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  authDomain: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  databaseURL: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  projectId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  storageBucket: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  messagingSenderId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION",
  appId: "REPLACE_WITH_YOUR_Firebase_CONFIGURATION"
};

Finally, Firebase is initialized, and we create two global variables db and auth that refer to Firebase authentication and to Firebase realtime database.

// Initialize firebase
firebase.initializeApp(firebaseConfig);
      
// Make auth and database references
const auth = firebase.auth();
const db = firebase.database();

The following line allows us to use fontawesome icons:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">

The next includes a favicon on our web page.

<link rel="icon" type="image/png" href="favicon.png">

Finally, reference an external style.css file to format the HTML page.

<link rel="stylesheet" type="text/css" href="style.css">

We’re done with the metadata. Now, let’s go to the HTML parts that are visible to the user—go between the <body> and </body> tags.

We create a top “navigation” bar with the name of our app and a small icon from fontawesome.

<!--TOP BAR-->
<div class="topnav">
  <h1>ESP GPIO Control <i class="fas fa-lightbulb"></i></h1>
</div>

The following lines create a bar with the details of the authenticated user (email). It also shows a logout link to log out the user.

<div id="authentication-bar" style="display: none;">
  <p><span id="authentication-status">User logged in</span>
     <span id="user-details">USEREMAIL</span>
     <a href="/" id="logout-link">(logout)</a>
  </p>
</div>

First, we set the display style of all elements to none. We’ll hide and show content depending if the user is authenticated or not—we’ll handle that using JavaScript.

Next, the following lines create the login form with an input field for the email and an input field for the password:

<form id="login-form" style="display: none;">  
  <div class="form-elements-container">
    <label for="input-email"><b>Email</b></label>
    <input type="text" placeholder="Enter Username" id="input-email" required>
  
    <label for="input-password"><b>Password</b></label>
    <input type="password" placeholder="Enter Password" id="input-password" required>
          
    <button type="submit" id="login-button">Login</button>
    <p id="error-message" style="color:red;"></p>
  </div>
</form>

Inside the form, there’s also a paragraph to display an error message if the login fails.

<p id="error-message" style="color:red;"></p>

Finally, we create a grid to display different cards for the GPIOs.

<div class="content-sign-in" id="content-sign-in" style="display: none;">
  <div class="card-grid">

For example, the following lines create a card for GPIO 12:

<!--CARD FOR GPIO 12-->
<div class="card">
  <p class="card-title"><i class="fas fa-lightbulb"></i> GPIO 12</p>
  <p>
    <button class="button-on" id="btn1On">ON</button>
    <button class="button-off" id="btn1Off">OFF</button>
  </p>
  <p class="state">State:<span id="state1"></span></p>
</div>

The buttons have specific ids so that we can refer to them later on in the Javascript files. There’s also a span tag with a specific id to insert the GPIO state.

Creating the cards for the other GPIOs is similar:

<!--CARD FOR GPIO 13-->
<div class="card">
  <p class="card-title"><i class="fas fa-lightbulb"></i> GPIO 13</p>
  <p>
    <button class="button-on" id="btn2On">ON</button>
    <button class="button-off" id="btn2Off">OFF</button>
  </p>
  <p class="state">State:<span id="state2"></span></p>
</div>

<!--CARD FOR GPIO 14-->
<div class="card">
  <p class="card-title"><i class="fas fa-lightbulb"></i> GPIO 14</p>
  <p>
    <button class="button-on" id="btn3On">ON</button>
    <button class="button-off" id="btn3Off">OFF</button>
  </p>
  <p class="state">State:<span id="state3"></span></p>
</div>

It’s important to keep in mind the ids of each of those elements, so that’s its easier to identify them on the JavaScript file. You can use any other ids that make sense for your project.

GPIO 12GPIO 13GPIO 14
ON Buttonbtn1Onbtn2Onbtn3On
OFF Buttonbtn1Offbtn2Offbtn3Off
Statestate1state2state3

Finally, we need to add references to the external JavaScript files. For our application, we’ll create two JavaScript files: auth.js (that handles everything related to the authentication) and index.js that handles everything related to the UI. We’ll create those files inside a folder called scripts inside the public folder of our application.

<script src="scripts/auth.js"></script>
<script src="scripts/index.js"></script>

After making the necessary changes (inserting your firebaseConfig object), you can save the HTML file.

style.css

Inside the public folder create a file called style.css. To create the file, select the public folder, and then click on the +file icon at the top of the File Explorer. Call it style.css.

Create CSS File VS Code

Then, copy the following to the style.css file

html {
    font-family: Verdana, Geneva, Tahoma, sans-serif;
    display: inline-block;
    text-align: center;
}
h1 {
    font-size: 1.8rem;
    color: white;
}
.topnav {
    overflow: hidden;
    background-color: #049faa;
    color: white;
    font-size: 1rem;
    padding: 10px;
}
#authentication-bar{
    background-color:mintcream;
    padding-top: 10px;
    padding-bottom: 10px;
}

#user-details{
    color: cadetblue;
}

.content {
    padding: 20px;
}

body {
    margin: 0;
}

.card-grid {
    max-width: 800px;
    margin: 0 auto;
    display: grid;
    grid-gap: 2rem;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.card {
    background-color: white;
    box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5);
}

.card-title {
    font-size: 1.2rem;
    color: #034078
}
.state {
    color:#1282A2;
}
button {
    background-color: #049faa;
    color: white;
    padding: 14px 20px;
    margin: 8px 0;
    border: none;
    cursor: pointer;
    border-radius: 4px;
}
.button-on {
    background-color:#034078;
}
.button-on:hover {
    background-color: #1282A2;
}
.button-off {
    background-color:#858585;
}
.button-off:hover {
    background-color: #252524;
}

.form-elements-container{
    padding: 16px;
    width: 250px;
    margin: 0 auto;
}

input[type=text], input[type=password] {
    width: 100%;
    padding: 12px 20px;
    margin: 8px 0;
    display: inline-block;
    border: 1px solid #ccc;
    box-sizing: border-box;
}

View raw code

The CSS file includes some simple styles to make our webpage look better. We won’t discuss how CSS works in this tutorial. You can easily modify the CSS file to change the colors and font size, for example.

JavaScript Files

We’ll create two JavaScript files (auth.js and index.js) inside a scripts folder inside the public folder.

  • Select the public folder, then click on the +folder icon to create a new folder. Call scripts to that new folder.
  • Then, select the scripts folder and click on the +file icon. Create a file called auth.js. Then, repeat the previous steps to create an index.js.

The following image shows how your web app project folder structure should look like.

Firebase Project VS Code Folder File Structure

auth.js

Now let’s implement user sign-in using Firebase authentication. We’ll implement sign-in using email and password.

Copy the following to the auth.js file you created previously.

document.addEventListener("DOMContentLoaded", function(){

    // listen for auth status changes
    auth.onAuthStateChanged(user => {
        if (user) {
            console.log("user logged in");
            console.log(user);
            setupUI(user);
        } else {
            console.log("user logged out");
            setupUI();
        }
    });

    // login
    const loginForm = document.querySelector('#login-form');
    loginForm.addEventListener('submit', (e) => {
        e.preventDefault();
        // get user info
        const email = loginForm['input-email'].value;
        const password = loginForm['input-password'].value;
        // log the user in
        auth.signInWithEmailAndPassword(email, password).then((cred) => {
            // close the login modal & reset form
            loginForm.reset();
            console.log(email);
        })
        .catch((error) =>{
            const errorCode = error.code;
            const errorMessage = error.message;
            document.getElementById("error-message").innerHTML = errorMessage;
            console.log(errorMessage);
        });
    });

    // logout
    const logout = document.querySelector('#logout-link');
    logout.addEventListener('click', (e) => {
    e.preventDefault();
    auth.signOut();
    });
    
});

View raw code

Then, save the file. This file takes care of everything related to the login and logout of the user. Continue reading to learn how the code works or skip to the next section.

Login

The following lines are responsible for logging in the user.

const loginForm = document.querySelector('#login-form');
loginForm.addEventListener('submit', (e) => {
  e.preventDefault();
  // get user info
  const email = loginForm['input-email'].value;
  const password = loginForm['input-password'].value;
  // log the user in
  auth.signInWithEmailAndPassword(email, password).then((cred) => {
    // close the login modal & reset form
    loginForm.reset();
    console.log(email);
  })
  .catch((error) =>{
    const errorCode = error.code;
    const errorMessage = error.message;
    document.getElementById("error-message").innerHTML = errorMessage;
    console.log(errorMessage);
  });
});

We create a variable that refers to the login form HTML element called loginForm.

const loginForm = document.querySelector('#login-form');

If you go back to the index.html file, you can see that the form has the login-form id.

We add an event listener of type submit to the form. This means that the subsequent instructions will run whenever the form is submitted.

loginForm.addEventListener('submit', (e) => {

You can get the submitted data as follows.

const email = loginForm['input-email'].value;
const password = loginForm['input-password'].value;

If you go back to the HTML file, you’ll see that the input fields contain the following ids: input-email and input-password for the email and password, respectively.

Now that we have the inserted email and password, we can try to log in to Firebase. To do that, pass the user’s email address and password to the following method: signInWithEmailAndPassword:

auth.signInWithEmailAndPassword(email, password).then((cred) => {

After logging in, we reset the form and print the user email in the console.

auth.signInWithEmailAndPassword(email, password).then((cred) => {
  // close the login modal & reset form
  loginForm.reset();
  console.log(email);
})

In case there is an error signing in, we catch the error message, and display it on the error-message HTML element (a paragraph below the form).

.catch((error) =>{
  const errorCode = error.code;
  const errorMessage = error.message;
  document.getElementById("error-message").innerHTML = errorMessage;
  console.log(errorMessage);
});

Logout

The following snippet is responsible for logging out the user.

const logout = document.querySelector('#logout-link');
logout.addEventListener('click', (e) => {
  e.preventDefault();
  auth.signOut();
});

When the user is logged in, a logout link is visible in the authentication bar. That link has the logout-link id (see on the HTML file). So, first, we create a variable called logout that refers to the logout link.

const logout = document.querySelector('#logout-link');

Then, we add an event listener of type click. This means the subsequent instructions will run whenever you click on the logout link.

logout.addEventListener('click', (e) => {

When the button is clicked, we sign out the user using the signOut method.

auth.signOut();

Auth State Changes

To keep track of the user authentication state—to know if the user is logged in or logged out, there is a method called onAuthSateChanged that allows you to receive an event whenever the authentication state changes.

auth.onAuthStateChanged(user => {
  if (user) {
    console.log("user logged in");
    console.log(user);
    setupUI(user);
    var uid = user.uid;
    console.log(uid);
  } else {
    console.log("user logged out");
    setupUI();
  }
});

If the user returned is null, the user is currently signed out. Otherwise, it is currently signed in.

In both scenarios, we print the current user state to the console and call the setupUI() function. We haven’t created that function yet (we’ll create it in the next section), but it will be responsible for handling the user interface accordingly to the authentication state.

When the user is logged in, we pass the user as an argument to the setupUI() function. In this case, we’ll display the complete user interface to show the buttons to control the GPIOs, as you’ll see later.

if (user) {
  console.log("user logged in");
  console.log(user);
  setupUI(user);

If the user is logged out, we call the setupUI() function without any argument. In that scenario, we’ll simply display a message informing that the user is logged out and doesn’t have access to the interface (as we’ll see later).

} else {
  console.log("user logged out");
  setupUI();
}

index.js

The index.js file handles the UI – it shows the right content depending on the user authentication status. When the user is logged in, it gets the GPIO states from the database and updates the GPIO states on the interface. Then, it changes the states whenever you press the buttons.

Copy the following to the index.js file.

const loginElement = document.querySelector('#login-form');
const contentElement = document.querySelector("#content-sign-in");
const userDetailsElement = document.querySelector('#user-details');
const authBarElement = document.querySelector("#authentication-bar");

// Elements for GPIO states
const stateElement1 = document.getElementById("state1");
const stateElement2 = document.getElementById("state2");
const stateElement3 = document.getElementById("state3");

// Button Elements
const btn1On = document.getElementById('btn1On');
const btn1Off = document.getElementById('btn1Off');
const btn2On = document.getElementById('btn2On');
const btn2Off = document.getElementById('btn2Off');
const btn3On = document.getElementById('btn3On');
const btn3Off = document.getElementById('btn3Off');

// Database path for GPIO states
var dbPathOutput1 = 'board1/outputs/digital/12';
var dbPathOutput2 = 'board1/outputs/digital/13';
var dbPathOutput3 = 'board1/outputs/digital/14';

// Database references
var dbRefOutput1 = firebase.database().ref().child(dbPathOutput1);
var dbRefOutput2 = firebase.database().ref().child(dbPathOutput2);
var dbRefOutput3 = firebase.database().ref().child(dbPathOutput3);

// MANAGE LOGIN/LOGOUT UI
const setupUI = (user) => {
  if (user) {
    //toggle UI elements
    loginElement.style.display = 'none';
    contentElement.style.display = 'block';
    authBarElement.style.display ='block';
    userDetailsElement.style.display ='block';
    userDetailsElement.innerHTML = user.email;

    //Update states depending on the database value
    dbRefOutput1.on('value', snap => {
        if(snap.val()==1) {
            stateElement1.innerText="ON";
        }
        else{
            stateElement1.innerText="OFF";
        }
    });
    dbRefOutput2.on('value', snap => {
        if(snap.val()==1) {
            stateElement2.innerText="ON";
        }
        else{
            stateElement2.innerText="OFF";
        }
    });
    dbRefOutput3.on('value', snap => {
        if(snap.val()==1) {
            stateElement3.innerText="ON";
        }
        else{
            stateElement3.innerText="OFF";
        }
    });

    // Update database uppon button click
    btn1On.onclick = () =>{
        dbRefOutput1.set(1);
    }
    btn1Off.onclick = () =>{
        dbRefOutput1.set(0);
    }

    btn2On.onclick = () =>{
        dbRefOutput2.set(1);
    }
    btn2Off.onclick = () =>{
        dbRefOutput2.set(0);
    }

    btn3On.onclick = () =>{
        dbRefOutput3.set(1);
    }
    btn3Off.onclick = () =>{
        dbRefOutput3.set(0);
    }

  // if user is logged out
  } else{
    // toggle UI elements
    loginElement.style.display = 'block';
    authBarElement.style.display ='none';
    userDetailsElement.style.display ='none';
    contentElement.style.display = 'none';
  }
}

View raw code

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

Getting HTML Elements

First, we create variables to refer to several elements on the UI interface by referring to their ids. To identify these elements, we recommend that you take a look at the HTML file provided and find the elements with the referred ids.

const loginElement = document.querySelector('#login-form');
const contentElement = document.querySelector("#content-sign-in");
const userDetailsElement = document.querySelector('#user-details');
const authBarElement = document.querySelector("#authentication-bar");

The loginElement corresponds to the login form. The contentElement corresponds to the section of the web page that is visible when the user is logged in (that shows the sensor readings). The userDetailsElement corresponds to a section that will display the email of the logged in user. The auhtBarElement corresponds to the authentication bar that shows the current user status, the email of the authenticated user and the logout link.

The following creates variables to refer to the buttons as GPIOs states on the interface:

// Elements for GPIO states
const stateElement1 = document.getElementById("state1");
const stateElement2 = document.getElementById("state2");
const stateElement3 = document.getElementById("state3");

// Button Elements
const btn1On = document.getElementById('btn1On');
const btn1Off = document.getElementById('btn1Off');
const btn2On = document.getElementById('btn2On');
const btn2Off = document.getElementById('btn2Off');
const btn3On = document.getElementById('btn3On');
const btn3Off = document.getElementById('btn3Off');

Database Paths and References

Then, we need to create database paths to where the GPIOs states are saved:

// Database path for GPIO states
var dbPathOutput1 = 'board1/outputs/digital/12';
var dbPathOutput2 = 'board1/outputs/digital/13';
var dbPathOutput3 = 'board1/outputs/digital/14';

To be able to interact with the database on those paths, we need to create database references using those paths:

// Database references
var dbRefOutput1 = firebase.database().ref().child(dbPathOutput1);
var dbRefOutput2 = firebase.database().ref().child(dbPathOutput2);
var dbRefOutput3 = firebase.database().ref().child(dbPathOutput3);

sertupUI() Function

Then, we create the setupUI() function that will handle the UI accordingly to the state of the user authentication.

In the auth.js file, we called the setupUI() function with the user argument setupUI(user) if the user is logged in; or the function without argument setupUI() when the user is logged out.

So, let’s check what happens when the user is logged in.

if (user) {

We show the authentication bar (that shows the user details and the logout link). To do that, we can set its display style to block. We also want the web page’s main content with the sensor readings to be visible.

contentElement.style.display = 'block';
authBarElement.style.display ='block';

Finally, we can get the logged in user email with user.email and display it in the userDetailsElement section as follows:

userDetailsElement.innerHTML = user.email;

Get GPIO States

The following lines get the GPIO states whenever there’s a change in the database and update the corresponding HTML elements with the new values.

//Update states depending on the database value
dbRefOutput1.on('value', snap => {
  if(snap.val()==1) {
    stateElement1.innerText="ON";
  }
  else{
    stateElement1.innerText="OFF";
  }
});
dbRefOutput2.on('value', snap => {
  if(snap.val()==1) {
    stateElement2.innerText="ON";
  }
  else{
    stateElement2.innerText="OFF";
  }
});
dbRefOutput3.on('value', snap => {
  if(snap.val()==1) {
    stateElement3.innerText="ON";
  }
  else{
    stateElement3.innerText="OFF";
  }
});

Button Events

Then, we add events to the buttons to write 1 or 0 to the corresponding database path accordingly to the button that was pressed.

// Update database upon button click
btn1On.onclick = () =>{
  dbRefOutput1.set(1);
}
btn1Off.onclick = () =>{
  dbRefOutput1.set(0);
}

btn2On.onclick = () =>{
  dbRefOutput2.set(1);
}
btn2Off.onclick = () =>{
  dbRefOutput2.set(0);
}

btn3On.onclick = () =>{
  dbRefOutput3.set(1);
}
btn3Off.onclick = () =>{
  dbRefOutput3.set(0);
}

Logged Out UI

The following snippet handles the UI when the user logs out. We want to hide the authentication bar and the main webpage content (GPIO states and corresponding buttons) and show the login form.

} else{
  // toggle UI elements
  loginElement.style.display = 'block';
  authBarElement.style.display ='none';
  contentElement.style.display = 'none';
}

Favicon File

To display a favicon in your web app, you need to move the picture you want to use as favicon to the public folder. The picture should be called favicon.png. You can simply drag the favicon file from your computer into the public folder in VS Code.

We’re using the following icon as a favicon for our web app:

Deploy your App

After saving the HTML, CSS, and JavaScript files, deploy your app on VS Code by running the following command on the Terminal window.

firebase deploy

The Terminal should display something as follows:

ESP32 ESP8266 Deploy Firebase Web App

Firebase offers a free hosting service to serve your assets and web apps. Then, you can access your web app from anywhere.

You can use the Hosting URL provided to access your web app from anywhere.

Demonstration

Congratulations! You successfully deployed your app. It is now hosted on a global CDN using Firebase hosting. You can access your web app from anywhere on the Hosting URL provided. In my case, it is https://esp-firebase-demo.web.app.

Firebase Web App Control ESP32 ESP8266 GPIOs from Anywhere with Authentication

The web app is responsive, and you can access it using your smartphone, computer, or tablet.

Insert the email and password of the authorized user you added in the Firebase Authentication methods. After that, you can access the dashboard to control the ESP32 or ESP8266 GPIOs.

Go to your project’s Firebase console Hosting tab. You can see your app domains, deploy history, and you can even roll back to previous versions of your app.

Firebase Web App Deploy History and Domains

Wrapping Up

In this tutorial, you learned how to create a Firebase Web App with login/logout authentication to control the ESP32 or ESP8266 GPIOs from anywhere. The GPIO states are saved on the realtime database and the ESP is listening to any changes that occur in the database to update the GPIOs right away. You can easily add more GPIOs or more boards to this project.

The database is protected using database rules. Only authorized authenticated users can access the web app to control the GPIOs.

You can combine this project with other ESP32/ESP8266 Firebase projects we have published and add more functionalities to your app.

If you want to learn more about Firebase, we recommend taking a look at our new eBook, exclusively dedicated to this subject:

We have other resources related to ESP32 and ESP8266 that you may like:

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!

50 thoughts on “Control ESP32/ESP8266 GPIOs from Anywhere (Firebase Web App)”

  1. Hello Sara and Rui,
    This is an excellent tutorial, very detailed but logical and with helpful screen shots.
    I had to go slowly and everything worked perfectly the first time.
    There is a lot to absorb so I usually go through such tutorials at least twice to fully understand the processes.
    Also I am working through your Web Server Course which is helping me understand the commands and functions.
    Thank you both very much.
    Kind regards,
    Stephen

    Reply
    • Hi Stephen!
      That’s great!
      I’m really glad you’re enjoying the projects.
      Thank you so much for supporting our work.
      Regards,
      Sara

      Reply
  2. In spite of you giving an excellent description, I do encounter a problem. I did put in the html file, (and added my config), did add the 2 js files in their separate folder, did add the favicon, saved it, deployed it, but my webpage only shows the ‘Top bar’.
    The AUTHENTICATION BAR, LOGIN. and CONTENT BAR just do not show up.
    Did i overlook something?

    Reply
  3. Hi,
    Can you help me how I can supply externally my Nodemcu? When I try with 5v externally via VCC pin it does not work. But If I supply with usb I works.

    Reply
      • Selman, I presume you mean it does NOT communicate properly.
        I also presume that with Vcc you mean the pin labelled Vin.
        I am not entirely sure what you would mean with “it works but does not communicate properly”, but I will give it a go:
        Looking at the circuit The 5Volt coming from the USB is led via a Skottky diode to a voltage regulator that makes 3V3. The Vin pin is also connected to the input of that same regulator. The Skottky diode is there to protect your USB in case you are attaching a USB cable AND use an external PSU at the same time.
        This means that basically the 5Volt from the USB and the 5Volt from your PSU follow the same pathway. If it all works properly with USB but not with the 5V PSU there are basically 2 possibilities.
        1 Your Vin pin is not connected properly to the regulator (but that is easy to check: attach a USB cable and measure if there is approx 5 Volt on the Vin pin)
        2 your PSU stinks. Can it deliver the current needed? Try another PSU

        Reply
  4. Hi,

    For (1) Creating a Project Folder of Step (2) Setting Up a Firebase Web App Project (VS Code), it looks like there is a missing step. What is the step between creating a folder and typing firebase login on the terminal window?

    error message: The term ‘firebase’ is not recognized as the name of a cmdlet

    I also tried to npm install firebase but Im getting the same error.

    thanks,

    Reply
    • Hi.
      Please read the Prerequisites section and install all the required software before proceeding.
      Install Required Software
      Before getting started you need to install the required software to create the Firebase Web App. Here’s a list of the software you need to install (click on the links for instructions):

      Visual Studio Code
      Node.JS LTS version
      Install Node.js Extension Pack (VS Code)
      Install Firebase Tools

      Regards,
      Sara

      Reply
  5. With the risc of becoming tedious, let me again express gratitude and admiration for the enormous amount of work you have put in to this. Not just that, but it is also an extremely useful project. Not only does it allow WW access to all sorts of homebrew projects, but it can do that in one app, whereas previously I would have to go to various ESP run webservers.
    I have managed to add sensor data readouts which was quite simple (define a seperate datastream for sensordata going to the firebase) and with a 20min reset of the ESP to ‘solve’ the token problems mentioned earlier, everything works great.
    next steps for me to do:
    -pimp up the CSS a bit (matter of taste)
    -add a slider (this should not be too difficult)
    -add a graph for some of the sensor data. Now that might be a bit harder. I understand that in the firebase book you sell graphs and sliders are covered, so if I am wise I buy that, even if it was only as appreciation for your work, but may also be a challenge (with added satisfaction or frustration) to work it out myself. We’ll see. But again, really great work

    Reply
      • Thanks Sara, in the mean time I figured out a way how i would do it and maybe that is how you did it as well: as firebase does not seem to persist data, I would keep the last say 15 measurements with a time stamp in a JSON and store that in a field/record of firebase. Update that JSON with every new measurement and then feed that to say Highcharts. I had overlooked the article you linked to and surely will check it out. (and maybe see whether my idea was unique or not)

        Reply
  6. Hello,

    I have a problem with communication lost. If the communication lost, my nodemcu cannot connect again wifi automatically.
    I tried to solve this problem with Event Handler (WiFiEventStationModeDissconnected& event) but it didn’t work.
    Could you please help how can I connect my nodemcu automatically if the connection lost ?

    Thanks a lot.

    Reply
  7. Thanks for the tutorial Sara! I had a question – is it safe to leave the firebase config details client side in the index.html page? I have limited experience writing code and never really for web apps.

    Reply
  8. Hello,

    When I try to build an another site with visual studio code on firebase, I cannot review the files on the explorer window. Beause, firebase tool ask that “the files that you want to upload already exist, do you want to owerwrite?”
    I change my root site with “firebase use –add” but still I cannot.
    Could you please help how I can build an another site?

    Reply
    • Hi.
      You need to start everything from the start, including creating a new Project folder, open that folder, and then run all the commands to start a new project.
      Regards,
      Sara

      Reply
      • Hello Sara,

        Despite I begin from the very start, (removing vs code, firebase project and folder) when I try to initializate my site, there is still message which is the index.html already exists.
        whether I prefered not to overwrite or overwrite, vs code didn’t update the folders window.
        Regards.

        Reply
  9. I’d like to say thank you very much for next great tutorial! I followed lot of them and always went to the end with success. Like this time 🙂 There is one small thing which could complicate my project. The ESP8266 stopping collect the orders from server for about 30min. Is it any way to solve such problem? This problem has been already mentioned in comments. The ESP stays connected to wifi but the outputs doesn’t change.
    Thanks!!!

    Reply
  10. Hello Sara,
    I am trying to build an android app with xamarin forms to control the gpios using your example.
    If my child nodes are 12,13 and 14, xamarin does’t not support this. I need to build my nodes like node_one, node_two and node_three. Because xamarin forms support just integer variable.
    However, If I build my nodes with string, ESP does not support.
    How can I declare my outputs using string in your example?
    Thank you.

    Reply
  11. Hello Sara,

    I would like to ask you about variable type in this example, writing on the RTDB nodes.
    When I change nodes value with an Android app, ESP doesn’t get the package, so I cannot change state of the pins.
    Could you please what can be the possible cause for this problem?
    Thank you.

    Reply
      • Hello Sara,

        Yes, the Android app is changing the RTDB nodes. But when I change the node with Android app, like 12, the ESP does not change the pin 12’s state. However, if I change the node on Firebase Console manually or with the web app, the ESP works very well.

        Reply
        • And is your android app working properly? Does it actually change the database nodes?
          I’m not sure what might be causing the issue…
          I haven’t tried this project with an android app.
          Regards,
          Sara

          Reply
  12. Can firebase be used to control GPIO’s based on scheduled timed events? So for example I could have a light turn on between 7pm and 9pm. I’ve had a look everywhere on the internet but can’t find any examples. Is this possibly something you may look at in the near future?
    Regards,
    Will

    Reply
  13. I hope you will find time to add more to this excellent tutorial.
    Eg. I would like to learn how to use inputs from board2 to set the outputs on board1 via the firebase database.

    Reply
    • Hi.
      Thanks for the suggestion.
      For that, board 2 should write to the database.
      Board 1 listens to the database.
      Board 1 needs to be listening on the same paths that the other board is publishing.
      Take a look at all our Firebase tutorials and see if you can build it yourself: https://randomnerdtutorials.com/?s=firebase
      You can also take a look at the library’s basic examples and see if you can put it together: https://github.com/mobizt/Firebase-ESP-Client/tree/main/examples
      I hope this helps.
      Regards,
      Sara

      Reply
      • Thanks for your swift reply.
        I found some pieces here and other places, and managed to cook something that works.
        I have ordered some more NodeMCU cards, but I still only have 1 board, so to test it I did add some code to the example so that it will read GPIO 15 as an input, and then update the Firebase database to toggle the status of output1 (GPIO 12)

        I added the following lines to the upper part of the script:
        // Declare inputs
        const int input1 = 15;

        // internal variables
        int setonce = 0;

        And then I added thin into the loop part, just before the last ‘}’ character:

        //Read input on GPIO 15
        if (digitalRead(input1) == HIGH) {
        //setonce makes sure Firebase gets updated only after input have been changed
        if (setonce == 0) {
        Serial.println(“Input1 is high”);
        setonce = 1;
        //The output will be set depening on its status
        if (digitalRead(output1) == LOW) {
        Firebase.RTDB.setInt(&stream, F(“/board1/outputs/digital/12”),1);
        } else {
        Firebase.RTDB.setInt(&stream, F(“/board1/outputs/digital/12”),0);
        }
        }
        } else {
        //reset setonce when input1 goes low
        setonce = 0;
        }
        // add a short delay to avoid the loop running to fast
        delay(50);

        Reply
  14. Boa noite, tudo bom? Estou tentando colocar o codigo HTML porem a pagina web fica branca e so aparece a barra superior azul com o nome ESP GPIO Control, não aparece os formularios de login, ja testei o index separado e tambem nao apareceu, ficarei muito grato com a ajuda, obrigado.

    Reply
  15. after I install all the Visual Studio Code,Node.JS LTS version,Install Node.js Extension Pack (VS Code),Install Firebase Tools and run the ‘Initializing Web App Firebase Project’, I got the msg ‘Firebase initialization complete!’ and ‘Deploy complete!’. But I cannot find the 404.html or the index.html and all the other stuffs from the left side bar of the VS studio, help plz, thank you

    Reply
  16. hi sara, your projects are of gr8 help, I made it on 1st go. But I have a question, like I want to create 2 different users with different board and no one can control or see each other. Is there a way?

    Reply
  17. Hello, I have completed the tutorial and it works well for me.
    I have been testing if it was possible that by pressing a single button, it activates a relay, leaves it activated for a second, and then deactivates it again.
    My intention is to use it to open a garage door, but I can’t, and I was wondering if you could help me. So far the only thing I have achieved is that when I press it, the relay is activated, and in a very few milliseconds it turns off, but I needed the relay to be activated for longer.
    I attached the button code:
    btn6On.onclick = () =>{
    dbRefOutput5.set(1);
    setTimeout(() => {
    console.log(“1 second please”)
    }, 1000);
    dbRefOutput5.set(0);
    }

    PD: With this code the relay is activated, but it turns off immediately, and the 1 second delay does not do it for me, I hope you can help me, I have already looked elsewhere and I cannot find the solution.
    I would greatly appreciate your help. Good morning.

    Reply

Leave a Reply to Ed Cancel reply

Download Our Free eBooks and Resources

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