How to Control an LED with NodeMCU and Firebase
A Complete Beginner’s Guide — From Installing Arduino IDE to Building a Live Web Dashboard
Difficulty: Beginner | Time: 2–3 Hours | Cost: Under $5
Introduction
Have you ever wanted to control a light bulb, fan, or any electronic device from your phone or computer — from anywhere in the world? That is exactly what you will learn in this tutorial.
We will use a tiny and affordable WiFi-enabled microcontroller called the NodeMCU (ESP8266), connect it to Google’s Firebase cloud database, and build a real-time web dashboard that can toggle an LED on and off with a single click — from any browser, anywhere on Earth.
This guide is written for absolute beginners. We will explain every single step in plain, simple language — just like explaining to someone who has never touched electronics or code before.
What You Will Learn in This Tutorial
- How to install Arduino IDE and set it up for NodeMCU
- How to install the correct USB driver for generic NodeMCU boards
- How to wire an LED to a NodeMCU safely
- How to set up a Firebase Realtime Database
- How to upload code to NodeMCU that reads from Firebase
- How to build a beautiful web dashboard to control the LED
What You Need (Hardware)
Before we start, gather these items. They are all very cheap and easily available online or at any electronics store.
- 1x NodeMCU ESP8266 board (generic/cheap version is fine)
- 1x LED (any color)
- 1x 220 ohm resistor (the one with Red-Red-Brown color bands)
- 2x Jumper wires (male to male)
- 1x USB cable (IMPORTANT: must be a data cable, not a charge-only cable)
- 1x Breadboard (optional but recommended for beginners)
💡 Charge-only USB cables are the number one hidden reason NodeMCU is not detected by your computer. Always use a cable that came with a phone or data device.
PART 1: Setting Up Arduino IDE
Step 1 — Download and Install Arduino IDE
Arduino IDE is the software you will use to write code and upload it to the NodeMCU. Think of it like Microsoft Word, but instead of writing documents you write programs for tiny computers.
- Go to the official website: https://www.arduino.cc/en/software
- Click on your operating system: Windows, Mac, or Linux
- Download the installer file
- Run the installer and follow the on-screen instructions
- When installation finishes, open Arduino IDE
✅ During installation on Windows, it may ask you to install drivers. Click Install All — this is important.
Step 2 — Install the CP2102 or CH340 USB Driver
Generic NodeMCU boards use a USB-to-Serial chip to communicate with your computer. There are two common chips used:
- CP2102 (Silicon Labs chip — very common in cheap NodeMCU boards)
- CH340G (another common chip in generic boards)
Your computer needs a special driver installed to recognize these chips. Without the driver, the NodeMCU will not appear in Arduino IDE and you cannot upload code.
How to Know Which Chip You Have
- Plug your NodeMCU into your computer via USB
- Right-click on the Windows Start button and click Device Manager
- Look under Ports (COM and LPT) or Other Devices
- If you see a yellow warning triangle, you need to install the driver
- The name shown tells you which chip you have: CP210x = CP2102, or CH34x = CH340
Installing CP2102 Driver (Silicon Labs)
- Go to: https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers
- Click the Downloads tab
- Download CP210x Windows Drivers
- Unzip the downloaded folder
- Run CP210xVCPInstaller_x64.exe (use x86 if you have a 32-bit Windows)
- Follow the installer steps and click Finish
- Restart your computer
Installing CH340 Driver
- Go to: https://sparks.gogo.co.nz/ch340.html
- Download the version for your OS
- Unzip and run the installer
- Restart your computer
Verify the Driver Worked
After restarting, open Device Manager again. Under Ports (COM and LPT) you should now see:
Silicon Labs CP210x USB to UART Bridge (COM3)
The COM number may be different on your computer — that is normal. The important thing is that it appears without a yellow warning triangle.
Step 3 — Add ESP8266 Board to Arduino IDE
By default, Arduino IDE only supports official Arduino boards. We need to add support for the ESP8266 chip used in NodeMCU. This is done by adding a Board Manager URL.
Add the Board Manager URL
- Open Arduino IDE
- Click File in the top menu, then click Preferences
- Find the field labeled Additional Board Manager URLs
- Paste this URL exactly into that field:
http://arduino.esp8266.com/stable/package_esp8266com_index.json
- Click OK to save
Install the ESP8266 Board Package
- Click Tools in the top menu
- Click Board, then click Boards Manager
- In the search box type: esp8266
- Find the package named ESP8266 by ESP8266 Community
- Click Install and wait — it downloads about 150MB so it may take a few minutes
- When done, close the Boards Manager
Select Your Board and Port
- Click Tools, then Board, then ESP8266 Boards
- Select NodeMCU 1.0 (ESP-12E Module)
- Click Tools, then Port
- Select the COM port that appeared after installing the driver (e.g., COM3 or COM4)
✅ If you do not see any COM port listed, your USB cable is charge-only. Replace it with a proper data cable.
Step 4 — Test With the Blink Example
Before moving on, let us verify everything works by uploading a simple test program.
- Click File, then Examples, then 01.Basics, then Blink
- The Blink code opens in a new window
- Click the Upload button (the right-pointing arrow icon)
- Wait for the process to finish — you will see Done uploading at the bottom
The built-in LED on your NodeMCU board should now be blinking on and off every second. If it is, your Arduino IDE setup is complete and working perfectly!
✅ Congratulations! Your NodeMCU is talking to your computer successfully.
PART 2: Wiring the LED to NodeMCU
Understanding the Circuit
An LED (Light Emitting Diode) is a tiny light that only allows electricity to flow in one direction. It has two legs:
- Longer leg = Positive (called Anode, marked with +)
- Shorter leg = Negative (called Cathode, marked with -)
We also use a 220 ohm resistor in series with the LED. This is very important — without the resistor, too much current flows through the LED and it will burn out instantly.
Wiring Instructions
Follow these connections carefully:
| FROM | TO | NOTES |
| NodeMCU D1 pin | 220Ω Resistor (one end) | GPIO5 on ESP8266 |
| Resistor (other end) | LED longer leg (+) | Anode / positive leg |
| LED shorter leg (-) | NodeMCU GND pin | Cathode / negative leg |
⚠️ Never connect an LED directly without a resistor. The resistor limits current and protects both the LED and the NodeMCU pin from burning out.
PART 3: Setting Up Firebase
Firebase is a cloud service by Google. Think of it as a tiny database living on the internet. Our NodeMCU will check this database every second to see if the LED should be ON or OFF, and our web dashboard will write to it when we press a button.
Step 1 — Create a Firebase Project
- Go to https://console.firebase.google.com
- Sign in with your Google account
- Click Add Project
- Enter a project name (e.g., G-ESP-LED)
- Choose whether to enable Google Analytics (optional for this project)
- Click Create Project and wait for it to finish
- Click Continue when ready
Step 2 — Create a Realtime Database
- In the left sidebar, click Build, then Realtime Database
- Click Create Database
- Choose your server location (United States is fine)
- Select Start in test mode — this allows reading and writing freely
- Click Enable
You will now see your empty database with a URL at the top that looks like:
https://your-project-name-default-rtdb.firebaseio.com
Copy and save this URL. You will need it in the Arduino code.
Step 3 — Get Your API Key
Step 3 – Get your API Key
- Click the gear icon ⚙️ (top left) → Project Settings
- Under “Your apps” section → General tab
- Scroll down to find “Web API Key”
AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXX
Copy and save this too!
Once you have both:
- ✅ Database URL
- ✅ API Key
Add a Web App to get API Key
- In “Your apps” section you see the platform icons:
iOS | Android | </> Web | Unity | Flutter
- Click “</> Web” (the code icon)
- App nickname: type anything e.g.
esp-led-web - Don’t check Firebase Hosting
- Click “Register app”
You’ll then see a config block like this:
js
const firebaseConfig = {
apiKey: "AIzaSyXXXXXXXXXXXXXXXXX", // ← COPY THIS
authDomain: "g-esp-led.firebaseapp.com",
databaseURL: "https://g-esp-led-default-rtdb.firebaseio.com",
projectId: "g-esp-led",
...
};
Copy the apiKey value
- Click the gear icon (top left next to Project Overview)
- Click Project Settings
- Scroll down to the Your Apps section
- Click the web icon (looks like: </>)
- Enter any nickname for the app (e.g., esp-led-web)
- Click Register App
- You will see a code block — find the apiKey value:
apiKey: “AIzaSyXXXXXXXXXXXXXXXXXXXXXXX”
- Copy this key and save it
- Click Continue to Console
💡 Your API key is safe to use in this project. We will add database rules to limit what anyone can do with it.
Just ignore the SDK setup — that’s for web developers, you don’t need it!
Choose “Use a <script> tag” then click “Continue to console”.
Step 4 — Set Database Security Rules
By default, Firebase may block all reads and writes after the test mode period expires. We need to set rules that allow our LED path to be read and written freely, while protecting everything else.
- In Firebase Console, go to Realtime Database
- Click the Rules tab
- Replace everything with this exactly (no quotes around true/false):
{
"rules": {
"led": {
".read": true,
".write": true
},
"$other": {
".read": false,
".write": false
}
}
}
- Click Publish
⚠️ Make sure true and false do NOT have quotes around them. Writing “true” in quotes means something completely different to Firebase and will leave your database wide open to anyone.
Step 5 — Create the LED Data Path
- Click the Data tab in Realtime Database
- Click the + button next to your database URL
- In the Key field type: led/state
- In the Value field select type: Boolean, then choose false
- Press Enter to confirm
Your database should now show:
{
"led": {
"state": false
}
}
led
state: false
Step 2 – Control the LED from Firebase!
- Go to Firebase Console → your project
- Click “Realtime Database” from left sidebar
- You’ll see the database is empty, click the “+” button
- Add:
- Key:
led - Click “+” again inside it
- Key:
state→ Value:true
- Key:
- Click Add/Confirm ✅
Add data like this step by step:
Field: led
Value: (leave empty, press + to nest)
└── Field: state
Value: false
Visually it should look like:
g-esp-led-default-rtdb
└── led
└── state: false
If you can’t find the + button
Try this instead — click the 3 dots menu ⋮ next to your database URL → “Import JSON” → paste this:
json
{
"led": {
"state": false
}
}
Click Import ✅
PART 4: Installing the Firebase Library and Uploading Code
Step 1 — Install the Firebase ESP8266 Library
Just like your phone downloads apps, Arduino IDE can download and install code libraries — pre-written code that handles complex tasks like connecting to Firebase.
- Open Arduino IDE
- Click Sketch in the top menu
- Click Include Library, then Manage Libraries
- In the search box type: Firebase ESP8266 Client
- Find Firebase ESP8266 Client by Mobizt
- Click Install
- If it asks to install dependencies, click Install All
If the library does not appear in search, install it manually:
- Go to: https://github.com/mobizt/Firebase-ESP8266/archive/refs/heads/master.zip
- Download the ZIP file
- In Arduino IDE click Sketch, Include Library, Add .ZIP Library
- Select the downloaded ZIP file
- Restart Arduino IDE
Step 2 — The Complete Arduino Code
Copy the following code into a new Arduino IDE sketch. Replace YOUR_WIFI_NAME and YOUR_WIFI_PASSWORD with your actual WiFi details.
#include <ESP8266WiFi.h>
#include <FirebaseESP8266.h>
// WiFi credentials
#define WIFI_SSID "YOUR_WIFI_NAME"
#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD"
// Firebase credentials
#define FIREBASE_HOST "your-project-default-rtdb.firebaseio.com"
#define FIREBASE_AUTH "YOUR_FIREBASE_API_KEY"
// LED Pin
#define LED_PIN D1
FirebaseData firebaseData;
FirebaseAuth auth;
FirebaseConfig config;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
// Connect to WiFi
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected! IP: " + WiFi.localIP().toString());
// Configure Firebase
config.host = FIREBASE_HOST;
config.signer.tokens.legacy_token = FIREBASE_AUTH;
Firebase.begin(&config, &auth);
Firebase.reconnectWiFi(true);
// Initialize LED state in Firebase (optional)
Firebase.setBool(firebaseData, "/led/state", false);
Serial.println("Firebase connected!");
}
void loop() {
if (Firebase.getBool(firebaseData, "/led/state")) {
bool ledState = firebaseData.boolData();
digitalWrite(LED_PIN, ledState ? HIGH : LOW);
Serial.println("LED is: " + String(ledState ? "ON" : "OFF"));
Serial.println(ledState ? "LED ON" : "LED OFF");
} else {
Serial.println("Firebase error: " + firebaseData.errorReason());
}
delay(1000); // Check every second
}
Step 3 — Upload the Code
- Make sure Tools > Board is set to NodeMCU 1.0 (ESP-12E Module)
- Make sure Tools > Port shows your COM port (e.g., COM4)
- Click the Upload arrow button
- Wait for Done uploading message at the bottom
Step 4 — Test Using Serial Monitor
- Click the magnifying glass icon in the top right of Arduino IDE
- Set the baud rate dropdown at the bottom to 115200
You should see messages like:
Connecting to WiFi…..
WiFi Connected! IP: 192.168.1.x
Firebase Connected!
LED OFF
LED OFF
Now go to Firebase Console > Realtime Database > Data and change the state value from false to true. Within 1 second your LED should light up and Serial Monitor should show LED ON.
✅ Your NodeMCU is now connected to the cloud! You are controlling hardware from the internet.
PART 5: Building the Web Dashboard
Now we will build a beautiful web page that lets you control the LED from any browser. The page connects directly to your Firebase database and updates in real time — no page refresh needed.
How the Dashboard Works
When you open the web page:
- The page connects to your Firebase Realtime Database
- It reads the current value of /led/state
- It shows the LED as ON (glowing) or OFF
- When you press the button, it writes true or false to Firebase
- NodeMCU reads that change within 1 second and turns the LED on or off
💡 The web page and the NodeMCU never talk to each other directly. They both only talk to Firebase. Firebase is the middleman.
Create the index.html File
Create a new file on your computer called index.html. Copy and paste the following complete code into it. Replace the Firebase config values with your own project values.
<!DOCTYPE html>
<html>
<head><title>LED Control</title></head>
<body>
<h2>LED Control</h2>
<button onclick="setLED(true)">Turn ON</button>
<button onclick="setLED(false)">Turn OFF</button>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.0.0/firebase-app.js";
import { getDatabase, ref, set } from "https://www.gstatic.com/firebasejs/10.0.0/firebase-database.js";
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "your-project.firebaseapp.com",
databaseURL: "https://your-project-default-rtdb.firebaseio.com",
projectId: "your-project-id",
};
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
window.setLED = function(state) {
set(ref(db, 'led/state'), state);
};
</script>
</body>
</html>
💡 The complete ready-to-use dashboard HTML file with beautiful styling, animated LED visual, real-time log, and toggle button is available to download from the link provided at the end of this article.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>G-ESP-LED Control</title>
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet"/>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--on: #00ffe7;
--off: #ff3c6e;
--bg: #0a0a0f;
--panel: #111118;
--border: #1e1e2e;
--text: #c8c8e0;
--dim: #444466;
}
body {
background: var(--bg);
font-family: 'Share Tech Mono', monospace;
color: var(--text);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
/* Animated grid background */
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
linear-gradient(rgba(0,255,231,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0,255,231,0.03) 1px, transparent 1px);
background-size: 40px 40px;
z-index: 0;
pointer-events: none;
}
.container {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
padding: 40px 20px;
width: 100%;
max-width: 420px;
}
/* Header */
.header {
text-align: center;
}
.header h1 {
font-family: 'Orbitron', sans-serif;
font-size: 1.6rem;
font-weight: 900;
letter-spacing: 0.15em;
color: #fff;
text-shadow: 0 0 20px rgba(0,255,231,0.4);
}
.header p {
font-size: 0.75rem;
color: var(--dim);
margin-top: 6px;
letter-spacing: 0.2em;
text-transform: uppercase;
}
/* Status badge */
.status-badge {
display: flex;
align-items: center;
gap: 8px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 999px;
padding: 6px 18px;
font-size: 0.7rem;
letter-spacing: 0.15em;
color: var(--dim);
}
.status-dot {
width: 8px; height: 8px;
border-radius: 50%;
background: var(--dim);
transition: background 0.3s, box-shadow 0.3s;
}
.status-dot.connected {
background: #00ffe7;
box-shadow: 0 0 8px #00ffe7;
animation: pulse-dot 2s infinite;
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* LED Visual */
.led-visual {
position: relative;
width: 160px; height: 160px;
display: flex; align-items: center; justify-content: center;
}
.led-ring {
position: absolute;
border-radius: 50%;
border: 1px solid var(--dim);
transition: all 0.6s ease;
}
.led-ring:nth-child(1) { width: 160px; height: 160px; opacity: 0.15; }
.led-ring:nth-child(2) { width: 120px; height: 120px; opacity: 0.25; }
.led-ring:nth-child(3) { width: 80px; height: 80px; opacity: 0.4; }
.led-bulb {
width: 56px; height: 56px;
border-radius: 50%;
background: #1a1a2e;
border: 2px solid var(--dim);
transition: all 0.6s ease;
position: relative;
z-index: 2;
}
/* ON state */
.led-visual.on .led-ring { border-color: var(--on); }
.led-visual.on .led-ring:nth-child(1) { opacity: 0.08; box-shadow: 0 0 60px 20px rgba(0,255,231,0.08); }
.led-visual.on .led-ring:nth-child(2) { opacity: 0.15; box-shadow: 0 0 40px 10px rgba(0,255,231,0.1); }
.led-visual.on .led-ring:nth-child(3) { opacity: 0.3; box-shadow: 0 0 20px 5px rgba(0,255,231,0.15); }
.led-visual.on .led-bulb {
background: radial-gradient(circle at 35% 35%, #ffffff, #00ffe7 40%, #00bfa5);
border-color: var(--on);
box-shadow: 0 0 30px 8px rgba(0,255,231,0.5), 0 0 60px 20px rgba(0,255,231,0.2);
animation: glow-pulse 2s ease-in-out infinite;
}
@keyframes glow-pulse {
0%, 100% { box-shadow: 0 0 30px 8px rgba(0,255,231,0.5), 0 0 60px 20px rgba(0,255,231,0.2); }
50% { box-shadow: 0 0 40px 14px rgba(0,255,231,0.7), 0 0 80px 30px rgba(0,255,231,0.3); }
}
/* OFF state */
.led-visual.off .led-bulb {
background: radial-gradient(circle at 35% 35%, #2a1a2a, #1a0a1a);
border-color: var(--off);
box-shadow: 0 0 10px 2px rgba(255,60,110,0.2);
}
.led-visual.off .led-ring { border-color: var(--off); opacity: 0.1; }
/* State label */
.state-label {
font-family: 'Orbitron', sans-serif;
font-size: 2rem;
font-weight: 700;
letter-spacing: 0.2em;
transition: color 0.4s, text-shadow 0.4s;
}
.state-label.on {
color: var(--on);
text-shadow: 0 0 20px rgba(0,255,231,0.6);
}
.state-label.off {
color: var(--off);
text-shadow: 0 0 20px rgba(255,60,110,0.4);
}
/* Toggle button */
.toggle-btn {
width: 100%;
padding: 18px;
border: none;
border-radius: 4px;
font-family: 'Orbitron', sans-serif;
font-size: 0.9rem;
font-weight: 700;
letter-spacing: 0.2em;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
text-transform: uppercase;
}
.toggle-btn::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1), transparent);
opacity: 0;
transition: opacity 0.3s;
}
.toggle-btn:hover::before { opacity: 1; }
.toggle-btn:active { transform: scale(0.98); }
.toggle-btn.turn-on {
background: transparent;
color: var(--on);
border: 1px solid var(--on);
box-shadow: 0 0 20px rgba(0,255,231,0.15), inset 0 0 20px rgba(0,255,231,0.05);
}
.toggle-btn.turn-on:hover {
background: rgba(0,255,231,0.08);
box-shadow: 0 0 30px rgba(0,255,231,0.3), inset 0 0 20px rgba(0,255,231,0.1);
}
.toggle-btn.turn-off {
background: transparent;
color: var(--off);
border: 1px solid var(--off);
box-shadow: 0 0 20px rgba(255,60,110,0.15), inset 0 0 20px rgba(255,60,110,0.05);
}
.toggle-btn.turn-off:hover {
background: rgba(255,60,110,0.08);
box-shadow: 0 0 30px rgba(255,60,110,0.3), inset 0 0 20px rgba(255,60,110,0.1);
}
.toggle-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
/* Info panel */
.info-panel {
width: 100%;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 4px;
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 10px;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.72rem;
}
.info-label { color: var(--dim); letter-spacing: 0.1em; }
.info-value { color: var(--text); letter-spacing: 0.05em; }
.info-value.highlight { color: var(--on); }
.divider {
height: 1px;
background: var(--border);
margin: 2px 0;
}
/* Log */
.log {
width: 100%;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 4px;
padding: 12px 16px;
max-height: 100px;
overflow-y: auto;
display: flex;
flex-direction: column-reverse;
gap: 4px;
}
.log::-webkit-scrollbar { width: 4px; }
.log::-webkit-scrollbar-track { background: transparent; }
.log::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
.log-entry {
font-size: 0.68rem;
color: var(--dim);
letter-spacing: 0.05em;
animation: fadeIn 0.3s ease;
}
.log-entry span { color: var(--on); margin-right: 8px; }
.log-entry.err span { color: var(--off); }
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; } }
/* Footer */
.footer {
font-size: 0.65rem;
color: var(--dim);
letter-spacing: 0.15em;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>G-ESP-LED</h1>
<p>NodeMCU Firebase Controller</p>
</div>
<div class="status-badge">
<div class="status-dot" id="statusDot"></div>
<span id="statusText">CONNECTING...</span>
</div>
<div class="led-visual" id="ledVisual">
<div class="led-ring"></div>
<div class="led-ring"></div>
<div class="led-ring"></div>
<div class="led-bulb"></div>
</div>
<div class="state-label off" id="stateLabel">OFF</div>
<button class="toggle-btn turn-on" id="toggleBtn" onclick="toggleLED()" disabled>
TURN ON
</button>
<div class="info-panel">
<div class="info-row">
<span class="info-label">PROJECT</span>
<span class="info-value">G-ESP-LED</span>
</div>
<div class="divider"></div>
<div class="info-row">
<span class="info-label">DATABASE</span>
<span class="info-value highlight">REALTIME</span>
</div>
<div class="divider"></div>
<div class="info-row">
<span class="info-label">PATH</span>
<span class="info-value">/led/state</span>
</div>
<div class="divider"></div>
<div class="info-row">
<span class="info-label">LAST CHANGED</span>
<span class="info-value" id="lastChanged">—</span>
</div>
</div>
<div class="log" id="log"></div>
<div class="footer">ESP8266 · FIREBASE RTDB · V1.0</div>
</div>
<!-- Firebase SDK -->
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/12.10.0/firebase-app.js";
import { getDatabase, ref, set, onValue } from "https://www.gstatic.com/firebasejs/12.10.0/firebase-database.js";
const firebaseConfig = {
apiKey: "AIzaSyD7ibgTkLlO2d1oqRelruLJTtLkaPwLpBI",
authDomain: "g-esp-led.firebaseapp.com",
databaseURL: "https://g-esp-led-default-rtdb.firebaseio.com",
projectId: "g-esp-led",
storageBucket: "g-esp-led.firebasestorage.app",
messagingSenderId: "80839921325",
appId: "1:80839921325:web:ec920365b88ca333df8024"
};
const app = initializeApp(firebaseConfig);
const db = getDatabase(app);
const ledRef = ref(db, '/led/state');
const statusDot = document.getElementById('statusDot');
const statusText = document.getElementById('statusText');
const ledVisual = document.getElementById('ledVisual');
const stateLabel = document.getElementById('stateLabel');
const toggleBtn = document.getElementById('toggleBtn');
const lastChanged= document.getElementById('lastChanged');
const logEl = document.getElementById('log');
let currentState = false;
function addLog(msg, isError = false) {
const now = new Date().toLocaleTimeString();
const entry = document.createElement('div');
entry.className = 'log-entry' + (isError ? ' err' : '');
entry.innerHTML = `<span>${now}</span>${msg}`;
logEl.prepend(entry);
if (logEl.children.length > 20) logEl.removeChild(logEl.lastChild);
}
function updateUI(state) {
currentState = state;
if (state) {
ledVisual.className = 'led-visual on';
stateLabel.className = 'state-label on';
stateLabel.textContent = 'ON';
toggleBtn.className = 'toggle-btn turn-off';
toggleBtn.textContent = 'TURN OFF';
} else {
ledVisual.className = 'led-visual off';
stateLabel.className = 'state-label off';
stateLabel.textContent = 'OFF';
toggleBtn.className = 'toggle-btn turn-on';
toggleBtn.textContent = 'TURN ON';
}
lastChanged.textContent = new Date().toLocaleTimeString();
}
// Listen for realtime changes
onValue(ledRef, (snapshot) => {
statusDot.classList.add('connected');
statusText.textContent = 'CONNECTED';
toggleBtn.disabled = false;
const val = snapshot.val();
updateUI(val === true);
addLog(`LED state synced → ${val ? 'ON' : 'OFF'}`);
}, (error) => {
statusDot.classList.remove('connected');
statusText.textContent = 'ERROR';
addLog('Firebase error: ' + error.message, true);
});
// Toggle function
window.toggleLED = function() {
const newState = !currentState;
toggleBtn.disabled = true;
set(ledRef, newState)
.then(() => {
addLog(`Command sent → ${newState ? 'ON' : 'OFF'}`);
toggleBtn.disabled = false;
})
.catch((err) => {
addLog('Write failed: ' + err.message, true);
toggleBtn.disabled = false;
});
};
</script>
</body>
</html>
Dashboard Features
- Live connection status indicator — shows CONNECTED or ERROR
- Animated LED bulb that glows cyan when ON and shows red when OFF
- One-click toggle button that changes color based on current state
- Activity log showing every command sent with timestamp
- Project info panel showing database path and last changed time
- Fully responsive — works on mobile and desktop
Testing the Dashboard Locally
- Save the index.html file to your computer
- Double-click the file to open it in your browser
- Wait a second for the CONNECTING status to change to CONNECTED
- Click the TURN ON button
- Your physical LED on the NodeMCU should light up within 1 second
- Click TURN OFF — the LED goes off
✅ The dashboard works from any browser on your local WiFi network. In the next article we will deploy it to the internet so you can control your LED from anywhere in the world.
Troubleshooting Common Problems
NodeMCU Not Detected by Computer
- Try a different USB cable — charge-only cables are the most common cause
- Install the CP2102 or CH340 driver as described in Part 1
- Try a different USB port on your computer
- Restart your computer after installing the driver
Upload Fails or Times Out
- Hold the FLASH button on the NodeMCU while clicking Upload, release when upload starts
- Make sure the correct COM port is selected in Tools > Port
- Make sure NodeMCU 1.0 (ESP-12E Module) is selected as the board
- Close the Serial Monitor before uploading — both cannot use the port at the same time
Serial Monitor Shows Error: path not exist
- The /led/state path does not exist in your Firebase database yet
- Go to Firebase Console > Realtime Database and create the path manually
- Set Key: led/state and Value: false (boolean type)
LED Does Not Respond After Changing Firebase Value
- Check your WiFi credentials in the code — are they correct?
- Make sure your Firebase database rules allow reading (see Part 3, Step 4)
- Check the Serial Monitor for error messages
- Make sure true/false in Firebase rules do NOT have quotes around them
Web Dashboard Shows Error Instead of Connected
- Check that your Firebase API key is correctly pasted into the HTML file
- Make sure your database URL is correct in the config
- Check your browser console (press F12) for specific error messages
- Verify the /led/state path exists in your database
Summary — What You Built
Congratulations! If you followed this guide from start to finish, you have built a complete IoT (Internet of Things) system from scratch. Here is what you accomplished:
- Installed and configured Arduino IDE for NodeMCU development
- Installed USB drivers and established communication with the board
- Wired a safe LED circuit with proper current-limiting resistor
- Created a Firebase Realtime Database with proper security rules
- Written and uploaded C++ code to a microcontroller
- Built a real-time web dashboard using HTML, CSS, and Firebase SDK
- Created a complete end-to-end IoT control system
This is not just a toy project. The same architecture — device reads from cloud database, web app writes to cloud database — is used in professional smart home systems, industrial automation, and commercial IoT products.
What to Build Next
- Part 2: Deploy your dashboard to Firebase Hosting and GitHub Pages (free, live on the internet)
- Part 3: Build a Flutter mobile app to control the LED from your Android or iOS phone
- Part 4: Add more relays to control fans, lights, and appliances
- Part 5: Add sensors (temperature, motion) and display readings on the dashboard
Enjoy the Project!