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.
This article is Part 2 of a previous tutorial. You need to complete one of the following tutorials before proceeding:
- Control ESP32 GPIOs from Anywhere using Firebase
- Control ESP8266 NodeMCU GPIOs from Anywhere using Firebase
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 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.
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:
- Control ESP32 GPIOs from Anywhere using Firebase
- Control ESP8266 NodeMCU GPIOs from Anywhere using Firebase
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.
2) Select the web app icon.
3) Give your app a name. Then, check the box next to √ Also set up Firebase Hosting for this App. Click Register app.
4) Then, copy the firebaseConfig object and save it because you’ll need it later.
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.
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.
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.
7) Allow Firebase CLI to access your google account.
8) After this, Firebase CLI login should be successful. You can close the browser window.
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.
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.
12) Select the option “Use an existing project”—it should be highlighted in blue—then, hit Enter.
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.
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
16) The Firebase project should now be initialized successfully. Notice that VS code created some essential files under your project folder.
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
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.
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>
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 12 | GPIO 13 | GPIO 14 | |
ON Button | btn1On | btn2On | btn3On |
OFF Button | btn1Off | btn2Off | btn3Off |
State | state1 | state2 | state3 |
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.
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;
}
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.
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();
});
});
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';
}
}
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:
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.
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.
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.
Hello
This looks like it has to be done via VS-Code, is there a way to do it without?
Hi.
Yes. But the easiest way is using VS Code, and I strongly recommend using VS Code.
Regards,
Sara
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
Hi Stephen!
That’s great!
I’m really glad you’re enjoying the projects.
Thank you so much for supporting our work.
Regards,
Sara
Your site has helped me tremendously with my projects. Thank you for sharing your knowledge with us!
You’re welcome!
Thank you for following our work.
Regards,
Sara
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?
if I change the ‘display:none’ property of the login Bar…yes I get the login screen, but filling out my credentials doesn’t alter anything. Maybe I totally see it wrong but shouldn’t at least the login NOT start with a ‘display;none’ property?
Sorry for my string of messages, but in the mean time i found i get 3 error messages
Uncaught ReferenceError: firebase is not defined
https://esp-firebase-d11df.web.app/:30
esp-firebase-d22df.web.app:30:7
Uncaught ReferenceError: firebase is not defined
https://esp-firebase-d11df.web.app/scripts/index.js:25
index.js:25:20
Uncaught ReferenceError: can’t access lexical declaration ‘auth’ before initialization
https://esp-firebase-d11df.web.app/scripts/auth.js:5
auth.js:5:5
esp-firebase-d11df.web.app is the url I received during my set up. Can’t really make heads or tail from ‘firebase is not defined.
Any suggestions?
Solved! I just redid the html and js files. Perhaps with earlier copy and paste, something went wrong.
Sorry for the string of messages and thanks for this great work
Ok.
Great! I’m glad everything is working fine now.
Regards
Sara
Hi.
Open the JavaScript console on your web browser and tell me what errors or warning messages you get.
Regards,
Sara
I get the following errors and have the same issue:
index.js:24 Uncaught SyntaxError: Identifier ‘dbPathOutput3’ has already been declared
auth.js:10 user logged out
subscribe.ts:239 ReferenceError: setupUI is not defined
at Object.next (auth.js:11:13)
at subscribe.ts:104:16
at subscribe.ts:233:11
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.
It works with 5v via vcc pin, but I does communicate properly.
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
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,
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
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
Hi.
Thanks for your nice words.
Yes, the eBook covers sliders and charts. But, it’s also a good idea to try to find things out by yourself. You’ll learn a lot along the way.
You can also take a look at the following free project that uses charts: https://randomnerdtutorials.com/esp32-esp8266-firebase-gauges-charts/
I hope this helps.
Regards,
Sara
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)
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.
https://randomnerdtutorials.com/solved-reconnect-esp8266-nodemcu-to-wifi/
Hello Sara,
Despite I have been trying for a few days to a find a solution with Eventhandler, the problem still has not changed. My nodemcu remains connected for a time between 1 hour to 4 hour, and after that the module disconnects.
If I move my module to another place, after a while (without restart), It can connect again sometimes, but not always.
If I restart it, the module reconnects quickly.
Thank you.
Hi.
Maybe there is some problem with your Wi-Fi network?
Are you using a Wi-Fi repeater? Sometimes wi-fi repeaters cause that behavior.
Regards,
Sara
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.
Hi.
I think this discussion will answer your question: https://stackoverflow.com/questions/35418143/how-to-restrict-firebase-data-modification
Regards,
Sara
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?
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
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.
Hello Sara,
I would realy appreciated if you could help me.
I removed and reinstall everything including my firebase project, vs code and extensions.
But again there is a message which is :
File public/404.html already exists. Overwrite?
File public/index.html already exists. Overwrite?
Whether I select NO or Yes, I cannot download theese files on my vs code window.
Also, I use firebase use –add but I cannot download once again.
Please help.
Hi.
I’m sorry but I can’t reproduce that error.
I’m not sure why it’s causing it.
I found some similar issues on the web, but I’m not sure if these are exactly the same problem:
https://stackoverflow.com/questions/50393223/firebase-hosting-not-generate-firebase-json
https://github.com/coreui/coreui-react/issues/55
Regards,
Sara
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!!!
Hi.
We recently updated the code to address that issue.
See the code on the other post: https://randomnerdtutorials.com/firebase-control-esp8266-nodemcu-gpios/
Regards,
Sara
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.
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.
What do you mean?
Is the Android app changing the RTDB nodes that the EPS is listening to?
Regards,
Sara
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.
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
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
Hi.
That time management would be done on the ESP code, not on the Firebase database.
Regards,
Sara
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.
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
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);
Hello Myren, how did you solve it? Everytime i tried to set the new value the setup() function executes again, so I don’t know what to do
Hi.
Its been a while since I set it all up so I dont remember every step in the process.
My setup is now with one NodeMCU receiver unit which have a 8 channel solidstate relay card connected so I can control 8 outputs.
I have 8 Wemos D1 Mini v3.0.0 with temp/humidity/pressure sensor that sends their values every 10 minute to the NodeMCU receiver. The NodeMCU compares the temperature value to check if it is higher or lower than the setpoint for the actual channel and sets the corresponding output on or off.
The NodeMCU also sends the values to the Firebase database, and also reads the setpoint values from the database. From the web-app webpage I can monitor all these 8 sensors and also change the setpoint values.
The NodeMCU also acts as a webserver, html code is nearly similar to the web-app, but it also have a timestamp so I can see when the values was last updated.
I did choose the Wemos boards for the sender units because their low standby power consumption, only about 8 microamp in deep standby. I set them to wake every 600 sec, transmit their value and return to deep sleep again. This way I can use 18650 3.7v batteries to power them and they lasts for months before recharge is needed.
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.
Olá.
Abra a consola de javascript para ver se existe algum erro.
Cumprimentos,
Sara
Favicon not working
Hi.
What happens exactly?
Can you provide more details?
Regards,
Sara
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
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?
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.
Hi, I have reach step 18 but found error loading firebase SDK please check console. Could you let me know the solution?