#include <WiFi.h>
#include <HTTPClient.h>
#include <ESP32Servo.h>
#include <WebServer.h> // ✅ Added WebServer library
// -------- WiFi Credentials --------
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// -------- ThingSpeak --------
String apiKey = "1RYGYRV7AAQQU676";
const char* server = "http://api.thingspeak.com/update";
// -------- Pins --------
#define TRIG_PIN 5
#define ECHO_PIN 18
#define LED_PIN 2
#define BUZZER_PIN 4
#define SERVO_PIN 13
Servo lidServo;
WebServer webServer(8080); // ✅ Create web server on port 80
// -------- Variables --------
long duration;
int distance;
int fillPercent;
bool lidOpen = false;
unsigned long lastThingSpeakUpdate = 0;
const long thingSpeakInterval = 15000;
// -------- HTML Template --------
const char* htmlTemplate = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>Smart Dustbin - Live Dashboard</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin: 0;
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #eee;
}
h1 {
color: #2c3e50;
margin: 0;
font-size: 2.5em;
}
.status-badge {
display: inline-block;
padding: 8px 20px;
background: #2ecc71;
color: white;
border-radius: 20px;
font-size: 0.9em;
margin-top: 10px;
}
.dashboard {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 30px;
}
.card {
background: #f8f9fa;
padding: 25px;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.08);
text-align: center;
}
.card-icon {
font-size: 3em;
margin-bottom: 15px;
}
.card-value {
font-size: 2.8em;
font-weight: bold;
color: #3498db;
margin: 10px 0;
}
.card-label {
color: #7f8c8d;
font-size: 1.1em;
text-transform: uppercase;
letter-spacing: 1px;
}
.progress-container {
margin: 30px 0;
}
.progress-bar {
height: 35px;
background: #ecf0f1;
border-radius: 17px;
overflow: hidden;
position: relative;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #2ecc71, #f1c40f, #e74c3c);
border-radius: 17px;
transition: width 0.5s ease;
width: %FILL_PERCENT%%;
}
.progress-text {
position: absolute;
width: 100%;
text-align: center;
line-height: 35px;
font-weight: bold;
color: #2c3e50;
}
.alert-box {
background: #ffeaa7;
padding: 20px;
border-radius: 10px;
margin: 20px 0;
border-left: 5px solid #fdcb6e;
display: %ALERT_DISPLAY%;
}
.controls {
display: flex;
justify-content: center;
gap: 15px;
margin: 30px 0;
}
.btn {
padding: 15px 30px;
background: #3498db;
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
font-size: 1.1em;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 10px;
}
.btn:hover {
background: #2980b9;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.4);
}
.btn-danger {
background: #e74c3c;
}
.btn-danger:hover {
background: #c0392b;
box-shadow: 0 5px 15px rgba(231, 76, 60, 0.4);
}
.btn-success {
background: #2ecc71;
}
.btn-success:hover {
background: #27ae60;
box-shadow: 0 5px 15px rgba(46, 204, 113, 0.4);
}
.cloud-status {
background: #2c3e50;
color: white;
padding: 20px;
border-radius: 10px;
margin-top: 30px;
}
.update-time {
text-align: center;
color: #95a5a6;
margin-top: 20px;
font-size: 0.9em;
}
.auto-refresh {
text-align: center;
margin-top: 20px;
}
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
}
.controls {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚮 Smart Dustbin Dashboard</h1>
<div class="status-badge">LIVE • UPDATING EVERY 2 SECONDS</div>
</div>
<div class="progress-container">
<div class="progress-bar">
<div class="progress-fill"></div>
<div class="progress-text">%FILL_PERCENT%% FULL</div>
</div>
</div>
<div class="dashboard">
<div class="card">
<div class="card-icon">📏</div>
<div class="card-value">%DISTANCE% cm</div>
<div class="card-label">Ultrasonic Distance</div>
</div>
<div class="card">
<div class="card-icon">🚪</div>
<div class="card-value">%LID_STATUS%</div>
<div class="card-label">Lid Status</div>
</div>
</div>
<div class="alert-box">
<strong>⚠️ ALERT:</strong> Dustbin is %FILL_LEVEL% full! %ALERT_MESSAGE%
</div>
<div class="controls">
<button class="btn" onclick="sendCommand('led')">
💡 Test LED
</button>
<button class="btn btn-danger" onclick="sendCommand('buzzer')">
🔊 Test Buzzer
</button>
<button class="btn btn-success" onclick="sendCommand('toggle')">
⚡ Toggle Lid
</button>
</div>
<div class="cloud-status">
<h3>📡 Cloud Integration Status</h3>
<p>✅ ThingSpeak Connected</p>
<p><strong>API Key:</strong> %API_KEY%</p>
<p><strong>Last Update:</strong> %LAST_UPDATE% seconds ago</p>
<p><strong>Next Update in:</strong> <span id="countdown">15</span> seconds</p>
</div>
<div class="update-time">
Last updated: <span id="updateTime">%TIMESTAMP%</span>
</div>
<div class="auto-refresh">
<button class="btn" onclick="location.reload()">
🔄 Refresh Now
</button>
<p style="margin-top: 10px; color: #7f8c8d;">
Auto-refreshes every 5 seconds
</p>
</div>
</div>
<script>
// Auto-refresh page every 5 seconds
setTimeout(() => {
location.reload();
}, 5000);
// Update countdown timer
let countdown = 15;
setInterval(() => {
countdown = countdown > 0 ? countdown - 1 : 15;
document.getElementById('countdown').textContent = countdown;
}, 1000);
// Update timestamp
function updateTime() {
const now = new Date();
document.getElementById('updateTime').textContent =
now.toLocaleTimeString();
}
// Send commands to ESP32
function sendCommand(cmd) {
fetch('/' + cmd)
.then(response => response.text())
.then(data => {
alert('Command sent: ' + cmd + '\nResponse: ' + data);
})
.catch(error => {
console.error('Error:', error);
alert('Command failed');
});
}
// Initialize
updateTime();
setInterval(updateTime, 1000);
// WebSocket-like polling for real-time updates
function updateData() {
fetch('/data')
.then(response => response.json())
.then(data => {
document.querySelector('.card-value:nth-child(1)').textContent =
data.distance + ' cm';
document.querySelector('.progress-fill').style.width =
data.fillPercent + '%';
document.querySelector('.progress-text').textContent =
data.fillPercent + '% FULL';
});
}
// Poll every 2 seconds for real-time updates
setInterval(updateData, 2000);
</script>
</body>
</html>
)rawliteral";
// -------- Function Declarations --------
int getDistance();
void handleRoot();
void handleData();
void handleLED();
void handleBuzzer();
void handleToggle();
void setup() {
Serial.begin(115200);
Serial.println("\n========================================");
Serial.println(" SMART DUSTBIN SYSTEM");
Serial.println("========================================");
// Initialize pins
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
// Initialize servo
lidServo.attach(SERVO_PIN);
lidServo.write(0);
// Connect to WiFi
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n✅ WiFi Connected!");
Serial.print("📡 IP Address: ");
Serial.println(WiFi.localIP());
// ✅ Setup Web Server Routes
webServer.on("/", handleRoot);
webServer.on("/data", handleData);
webServer.on("/led", handleLED);
webServer.on("/buzzer", handleBuzzer);
webServer.on("/toggle", handleToggle);
webServer.begin();
Serial.println("🌐 Web Server Started!");
Serial.println("👉 Open browser and go to: http://" + WiFi.localIP().toString());
Serial.println("\n📊 **DASHBOARD ACCESS:**");
Serial.println("==========================================");
Serial.println("1. Copy this IP: " + WiFi.localIP().toString());
Serial.println("2. Paste in browser: http://" + WiFi.localIP().toString());
Serial.println("3. Dashboard will auto-refresh every 5 seconds");
Serial.println("4. Click buttons to control dustbin remotely");
Serial.println("==========================================");
Serial.println("\n🔗 **TEST THESE LINKS:**");
Serial.println("1. http://localhost:8080");
Serial.println("2. http://127.0.0.1:8080");
Serial.println("3. http://wokwi.local:8080");
Serial.println("4. http://10.10.0.2:8080");
Serial.println("5. http://10.10.0.2");
}
void loop() {
webServer.handleClient(); // ✅ Handle web client requests
// Read ultrasonic sensor
distance = getDistance();
distance = constrain(distance, 0, 40);
// Calculate fill percentage
fillPercent = ((40 - distance) * 100) / 40;
// Print to Serial Monitor
static unsigned long lastSerialPrint = 0;
if (millis() - lastSerialPrint > 2000) {
Serial.print("📏 Distance: ");
Serial.print(distance);
Serial.print(" cm | 📊 Fill: ");
Serial.print(fillPercent);
Serial.println(" %");
Serial.print("🚪 Lid: ");
Serial.print(lidOpen ? "OPEN" : "CLOSED");
Serial.print(" | 💡 LED: ");
Serial.print(fillPercent >= 75 ? "ON" : "OFF");
Serial.print(" | 🔊 Buzzer: ");
Serial.println(fillPercent >= 75 ? "ON" : "OFF");
Serial.println("------------------------------------------");
lastSerialPrint = millis();
}
// Automatic lid control (hand detection)
if (distance <= 15 && !lidOpen) {
lidServo.write(90);
lidOpen = true;
Serial.println("🖐️ Hand detected - Lid opened");
} else if (distance > 15 && lidOpen) {
lidServo.write(0);
lidOpen = false;
Serial.println("🤚 Hand removed - Lid closed");
}
// Alert system
if (fillPercent >= 75) {
digitalWrite(LED_PIN, HIGH);
digitalWrite(BUZZER_PIN, HIGH);
} else {
digitalWrite(LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
}
// Send to ThingSpeak every 15 seconds
if (millis() - lastThingSpeakUpdate > thingSpeakInterval) {
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
String url = String(server) + "?api_key=" + apiKey +
"&field1=" + String(distance) +
"&field2=" + String(fillPercent);
http.begin(url);
int httpCode = http.GET();
if (httpCode > 0) {
Serial.print("☁️ ThingSpeak Update Sent - Response: ");
Serial.println(httpCode);
}
http.end();
lastThingSpeakUpdate = millis();
}
}
delay(100);
}
int getDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
duration = pulseIn(ECHO_PIN, HIGH);
return duration * 0.034 / 2;
}
// ✅ Web Server Handler Functions
void handleRoot() {
String html = String(htmlTemplate);
// Replace placeholders with actual values
html.replace("%DISTANCE%", String(distance));
html.replace("%FILL_PERCENT%", String(fillPercent));
html.replace("%LID_STATUS%", lidOpen ? "OPEN" : "CLOSED");
html.replace("%API_KEY%", apiKey);
html.replace("%LAST_UPDATE%", String((millis() - lastThingSpeakUpdate) / 1000));
html.replace("%TIMESTAMP%", String(millis() / 1000));
// Alert logic
if (fillPercent >= 90) {
html.replace("%FILL_LEVEL%", "CRITICALLY");
html.replace("%ALERT_MESSAGE%", "Please empty immediately!");
html.replace("%ALERT_DISPLAY%", "block");
} else if (fillPercent >= 75) {
html.replace("%FILL_LEVEL%", "VERY");
html.replace("%ALERT_MESSAGE%", "Consider emptying soon.");
html.replace("%ALERT_DISPLAY%", "block");
} else {
html.replace("%ALERT_DISPLAY%", "none");
}
webServer.send(200, "text/html", html);
}
void handleData() {
// Return JSON data for AJAX requests
String json = "{";
json += "\"distance\":" + String(distance) + ",";
json += "\"fillPercent\":" + String(fillPercent) + ",";
json += "\"lidOpen\":" + String(lidOpen ? "true" : "false") + ",";
json += "\"ledStatus\":" + String(fillPercent >= 75 ? "true" : "false") + ",";
json += "\"timestamp\":" + String(millis());
json += "}";
webServer.send(200, "application/json", json);
}
void handleLED() {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
webServer.send(200, "text/plain", "LED toggled: " +
String(digitalRead(LED_PIN) ? "ON" : "OFF"));
}
void handleBuzzer() {
digitalWrite(BUZZER_PIN, HIGH);
delay(500);
digitalWrite(BUZZER_PIN, LOW);
webServer.send(200, "text/plain", "Buzzer tested!");
}
void handleToggle() {
lidOpen = !lidOpen;
lidServo.write(lidOpen ? 90 : 0);
webServer.send(200, "text/plain", "Lid " +
String(lidOpen ? "opened" : "closed"));
}