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://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">

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.



Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »

Build Web Server projects with the ESP32 and ESP8266 boards to control outputs and monitor sensors remotely. Learn HTML, CSS, JavaScript and client-server communication protocols DOWNLOAD »

Recommended Resources

Build a Home Automation System from Scratch » With Raspberry Pi, ESP8266, Arduino, and Node-RED.

Home Automation using ESP8266 eBook and video course » Build IoT and home automation projects.

Arduino Step-by-Step Projects » Build 25 Arduino projects with our course, even with no prior experience!

What to Read Next…


Enjoyed this project? Stay updated by subscribing our newsletter!

4 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

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.