🔧 Build Your Own AI Preventive Maintenance System Using ESP32, MPU6050, and Web Dashboard
🔧 Build Your Own AI Preventive Maintenance System Using ESP32, MPU6050, and Web Dashboard
📅 By Dinesh | LearnWithDTrend Blog | 2026
🚀 Introduction
Have you ever wanted to build a simple industrial machine monitoring system that can detect vibration changes, show live data on a display, and send the same data to a web page for remote monitoring?
In this project, I built a compact AI preventive maintenance system using:
✅ ESP32 microcontroller
✅ MPU6050 vibration sensor
✅ Potentiometer for motor speed control
✅ L298N motor driver
✅ BO motor
✅ I2C LCD display
✅ Buzzer for critical alert
✅ ESP32 web server
✅ CSV data download page
This project is designed like a small industrial machine monitoring setup, where vibration is continuously checked and abnormal conditions are detected early. When vibration becomes critical, the buzzer turns ON and the system alerts the user.
Let’s dive in.
🧠 Project Concept
Here’s how the system works:
The ESP32 reads vibration data from the MPU6050 sensor.
The potentiometer controls the BO motor speed through the L298N driver.
The LCD displays live temperature, humidity, motor speed, and vibration values.
The web dashboard shows the same data in a professional format.
If vibration crosses the critical threshold, the buzzer starts beeping.
The system also allows downloading the logged data as a CSV file, which can be opened in Excel for analysis.
This makes the project useful for demonstrating predictive maintenance, condition monitoring, and machine health tracking.
🧰 Components Used
| Component | Quantity |
|---|---|
| ESP32 DevKit | 1 |
| MPU6050 Sensor | 1 |
| Potentiometer | 1 |
| L298N Motor Driver | 1 |
| BO Motor | 1 |
| I2C LCD Display 16x2 | 1 |
| Active Buzzer | 1 |
| Jumper Wires | As needed |
| Breadboard | 1 |
| USB Cable | 1 |
| External Motor Power Supply | 1 |
🔌 Connections
ESP32 Pin Configuration
| Component | ESP32 Pin |
|---|---|
| MPU6050 SDA | GPIO 21 |
| MPU6050 SCL | GPIO 22 |
| Potentiometer Middle Pin | GPIO 34 |
| L298N IN1 | GPIO 26 |
| L298N IN2 | GPIO 27 |
| L298N ENA | GPIO 25 |
| Buzzer | GPIO 32 |
| LCD SDA | GPIO 21 |
| LCD SCL | GPIO 22 |
| LCD VCC | 5V |
| LCD GND | GND |
Important Notes
Use a common ground for all modules.
The MPU6050 and LCD share the same I2C pins.
The motor must be powered using an external supply through the L298N driver.
The buzzer is used only when the system detects critical vibration.
📟 ESP32 Code
The ESP32 reads vibration from the MPU6050, controls motor speed with the potentiometer, updates the LCD, activates the buzzer during critical vibration, and sends live data to the web dashboard.
#include <Wire.h>
#include <WiFi.h>
#include <WebServer.h>
#include <LiquidCrystal_I2C.h>
#include <math.h>
// ===================== WIFI =====================
const char* AP_SSID = "AI_Machine_Monitor";
const char* AP_PASS = "12345678";
// ===================== I2C PINS =====================
#define SDA_PIN 21
#define SCL_PIN 22
// ===================== MPU6050 =====================
#define MPU_ADDR 0x68
// ===================== POTENTIOMETER =====================
#define POT_PIN 34
// ===================== L298N MOTOR DRIVER =====================
#define IN1 26
#define IN2 27
#define ENA 25
// ===================== BUZZER =====================
#define BUZZER_PIN 32 // Active buzzer
// ===================== PWM SETTINGS =====================
#define PWM_FREQ 20000
#define PWM_RESOLUTION 8
// ===================== LCD =====================
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ===================== WEB SERVER =====================
WebServer server(80);
// ===================== SENSOR VARIABLES =====================
int16_t ax, ay, az;
float prevMag = 0;
float vibration = 0;
float temperature = NAN;
float humidity = NAN;
int potValue = 0;
int motorPWM = 0;
const float VIB_THRESHOLD_WARNING = 0.12;
const float VIB_THRESHOLD_CRITICAL = 0.18;
// ===================== BUZZER CONTROL =====================
bool buzzerState = false;
unsigned long lastBuzzerToggle = 0;
const unsigned long BUZZER_ON_TIME = 200;
const unsigned long BUZZER_OFF_TIME = 200;
// ===================== TIMERS =====================
unsigned long lastMpuRead = 0;
unsigned long lastEnvRead = 0;
unsigned long lastLcdUpdate = 0;
unsigned long lastHistoryUpdate = 0;
// ===================== HISTORY =====================
const int HISTORY_SIZE = 30;
float vibHistory[HISTORY_SIZE];
int historyIndex = 0;
// ===================== CSV LOG =====================
String csvData = "Time,Temperature,Humidity,MotorPWM,Vibration,Status\n";
unsigned long startMillis = 0;
// ===================== HTML PAGES =====================
const char DASHBOARD_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Predictive Maintenance Dashboard</title>
<style>
:root{
--bg:#07111f;
--panel:#0d1b2a;
--panel2:#10243b;
--text:#e8eef7;
--muted:#93a4bd;
--accent:#4fd1c5;
--accent2:#60a5fa;
--warn:#f59e0b;
--danger:#ef4444;
--ok:#22c55e;
--line:rgba(255,255,255,.08);
}
*{box-sizing:border-box}
body{
margin:0;
font-family:Inter,Segoe UI,Arial,sans-serif;
background:
radial-gradient(circle at top left, rgba(79,209,197,.18), transparent 28%),
radial-gradient(circle at top right, rgba(96,165,250,.18), transparent 26%),
linear-gradient(180deg, #050b14, #08111d 60%, #050b14);
color:var(--text);
}
.wrap{max-width:1200px;margin:0 auto;padding:24px}
.topbar{
display:flex;justify-content:space-between;align-items:center;gap:16px;
padding:18px 20px;border:1px solid var(--line);border-radius:22px;
background:rgba(13,27,42,.72);backdrop-filter:blur(10px);box-shadow:0 20px 60px rgba(0,0,0,.35);
}
.brand h1{margin:0;font-size:24px;letter-spacing:.4px}
.brand p{margin:6px 0 0;color:var(--muted);font-size:13px}
.badge{
padding:10px 14px;border-radius:999px;font-weight:700;font-size:12px;
background:rgba(79,209,197,.12);color:#7ff2e6;border:1px solid rgba(79,209,197,.35)
}
.grid{
display:grid;gap:18px;margin-top:18px;
grid-template-columns:repeat(12,1fr);
}
.card{
border:1px solid var(--line);border-radius:22px;background:rgba(13,27,42,.75);
box-shadow:0 14px 40px rgba(0,0,0,.25);padding:18px;
}
.hero{grid-column:span 12;display:grid;grid-template-columns:1.5fr 1fr;gap:18px;align-items:stretch}
.big-title{font-size:30px;line-height:1.1;margin:0 0 10px}
.sub{color:var(--muted);font-size:14px;line-height:1.6;margin:0}
.statusBox{
display:flex;flex-direction:column;justify-content:space-between;
background:linear-gradient(180deg, rgba(16,36,59,.95), rgba(13,27,42,.95));
}
.statusLabel{font-size:13px;color:var(--muted);margin-bottom:8px}
.statusValue{font-size:28px;font-weight:800;margin:0}
.statusValue.ok{color:var(--ok)}
.statusValue.warn{color:var(--warn)}
.statusValue.danger{color:var(--danger)}
.metrics{grid-column:span 12;display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
.metric{
padding:18px;border-radius:20px;background:linear-gradient(180deg, rgba(16,36,59,.95), rgba(9,19,31,.95));
border:1px solid var(--line);
}
.metric .name{color:var(--muted);font-size:13px}
.metric .value{font-size:30px;font-weight:800;margin-top:8px}
.metric .unit{font-size:13px;color:var(--muted);margin-left:6px}
.row{grid-column:span 12;display:grid;grid-template-columns:1.1fr .9fr;gap:18px}
.sectionTitle{margin:0 0 14px;font-size:18px}
.bar{
width:100%;height:16px;background:#0a1421;border-radius:999px;overflow:hidden;border:1px solid var(--line)
}
.fill{
height:100%;width:0;border-radius:999px;background:linear-gradient(90deg, var(--accent), var(--accent2));
transition:width .35s ease;
}
.detailList{display:grid;gap:10px}
.detail{
display:flex;justify-content:space-between;gap:20px;padding:12px 14px;border-radius:14px;
background:rgba(255,255,255,.03);border:1px solid var(--line);font-size:14px
}
.detail span{color:var(--muted)}
.nav{
display:flex;gap:10px;flex-wrap:wrap;margin-top:14px
}
a.btn{
text-decoration:none;color:var(--text);padding:11px 14px;border-radius:14px;
border:1px solid var(--line);background:rgba(255,255,255,.03);
}
a.btn.primary{background:linear-gradient(90deg, rgba(79,209,197,.18), rgba(96,165,250,.18))}
canvas{width:100%;height:280px;background:rgba(255,255,255,.02);border-radius:18px;border:1px solid var(--line)}
.footer{
margin:18px 0 6px;color:var(--muted);font-size:12px;text-align:center
}
@media (max-width: 900px){
.hero,.row{grid-template-columns:1fr}
.metrics{grid-template-columns:1fr 1fr}
}
@media (max-width: 560px){
.metrics{grid-template-columns:1fr}
.big-title{font-size:24px}
.statusValue{font-size:22px}
}
</style>
</head>
<body>
<div class="wrap">
<div class="topbar">
<div class="brand">
<h1>AI Preventive Maintenance Dashboard</h1>
<p>Industrial machine vibration monitoring with ESP32, MPU6050, motor control and live data download</p>
</div>
<div class="badge" id="wifiBadge">LIVE NODE ONLINE</div>
</div>
<div class="grid">
<div class="card hero">
<div>
<p class="sub">Machine asset tracking and vibration intelligence for a small industrial motor setup.</p>
<h2 class="big-title">Real-time health monitoring for predictive maintenance</h2>
<p class="sub">This project continuously checks vibration, temperature, humidity, and motor speed. When vibration rises above the set threshold, the system flags the machine for inspection.</p>
<div class="nav">
<a class="btn primary" href="/">Dashboard</a>
<a class="btn" href="/report">Maintenance Report</a>
<a class="btn" href="/download">Download Data</a>
</div>
</div>
<div class="card statusBox">
<div>
<div class="statusLabel">Machine Status</div>
<div class="statusValue ok" id="statusText">NORMAL</div>
</div>
<div>
<div class="statusLabel">Health Score</div>
<div class="statusValue" id="healthScore">100%</div>
</div>
</div>
</div>
<div class="metrics">
<div class="metric">
<div class="name">Temperature</div>
<div class="value"><span id="tempVal">--</span><span class="unit">°C</span></div>
</div>
<div class="metric">
<div class="name">Humidity</div>
<div class="value"><span id="humVal">--</span><span class="unit">%</span></div>
</div>
<div class="metric">
<div class="name">Motor Speed</div>
<div class="value"><span id="motorVal">--</span><span class="unit">PWM</span></div>
</div>
<div class="metric">
<div class="name">Vibration</div>
<div class="value"><span id="vibVal">--</span><span class="unit">g</span></div>
</div>
</div>
<div class="row">
<div class="card">
<h3 class="sectionTitle">Machine Health Indicator</h3>
<div class="bar"><div class="fill" id="healthBar"></div></div>
<p class="sub" style="margin-top:12px" id="recommendation">
System ready for live monitoring.
</p>
<div class="detailList" style="margin-top:16px">
<div class="detail"><span>Vibration Mode</span><strong id="vibMode">Normal</strong></div>
<div class="detail"><span>Sensor Node</span><strong>ESP32 + MPU6050</strong></div>
<div class="detail"><span>Actuator</span><strong>BO Motor via L298N</strong></div>
</div>
</div>
<div class="card">
<h3 class="sectionTitle">Live Vibration Trend</h3>
<canvas id="chart" width="700" height="320"></canvas>
</div>
</div>
</div>
<div class="footer">
AI Industrial Preventive Maintenance • ESP32 Web Dashboard • Final Year Project
</div>
</div>
<script>
const chart = document.getElementById('chart');
const ctx = chart.getContext('2d');
let historyData = [];
function drawChart(data){
const w = chart.width, h = chart.height;
ctx.clearRect(0,0,w,h);
ctx.strokeStyle = 'rgba(255,255,255,0.08)';
ctx.lineWidth = 1;
for(let i=1;i<6;i++){
const y = (h/6)*i;
ctx.beginPath();
ctx.moveTo(0,y); ctx.lineTo(w,y); ctx.stroke();
}
if(!data || data.length < 2){
ctx.fillStyle = 'rgba(255,255,255,0.65)';
ctx.font = '18px Segoe UI';
ctx.fillText('Waiting for vibration samples...', 24, 42);
return;
}
const maxV = Math.max(...data, 0.02);
const pad = 26;
const plotW = w - pad*2;
const plotH = h - pad*2;
ctx.fillStyle = 'rgba(255,255,255,0.55)';
ctx.font = '12px Segoe UI';
ctx.fillText(maxV.toFixed(3) + ' g', 8, 18);
ctx.fillText('0.000 g', 8, h-8);
ctx.strokeStyle = '#4fd1c5';
ctx.lineWidth = 3;
ctx.beginPath();
data.forEach((v, i) => {
const x = pad + (i/(data.length-1))*plotW;
const y = pad + (1 - v/maxV)*plotH;
if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
});
ctx.stroke();
ctx.fillStyle = '#60a5fa';
data.forEach((v, i) => {
const x = pad + (i/(data.length-1))*plotW;
const y = pad + (1 - v/maxV)*plotH;
ctx.beginPath(); ctx.arc(x,y,3.2,0,Math.PI*2); ctx.fill();
});
}
async function updateData(){
try{
const res = await fetch('/api/data');
const d = await res.json();
document.getElementById('tempVal').textContent = d.temperature === null ? '--' : d.temperature.toFixed(1);
document.getElementById('humVal').textContent = d.humidity === null ? '--' : d.humidity.toFixed(0);
document.getElementById('motorVal').textContent = d.motorPWM;
document.getElementById('vibVal').textContent = d.vibration.toFixed(3);
document.getElementById('healthScore').textContent = d.healthScore.toFixed(0) + '%';
document.getElementById('healthBar').style.width = d.healthScore + '%';
const statusText = document.getElementById('statusText');
statusText.textContent = d.status;
statusText.className = 'statusValue ' + d.statusClass;
document.getElementById('vibMode').textContent = d.status;
document.getElementById('recommendation').textContent = d.recommendation;
const hist = await fetch('/api/history');
historyData = await hist.json();
drawChart(historyData);
}catch(e){
document.getElementById('wifiBadge').textContent = 'CONNECTION LOST';
}
}
updateData();
setInterval(updateData, 1000);
window.addEventListener('resize', ()=>drawChart(historyData));
</script>
</body>
</html>
)rawliteral";
const char REPORT_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Maintenance Report</title>
<style>
:root{
--bg:#06111d; --panel:#0d1b2a; --line:rgba(255,255,255,.08);
--text:#edf4ff; --muted:#8ea3bf; --ok:#22c55e;
}
*{box-sizing:border-box}
body{
margin:0;font-family:Inter,Segoe UI,Arial,sans-serif;color:var(--text);
background:linear-gradient(180deg,#050b14,#08111d 60%,#050b14);
}
.wrap{max-width:1200px;margin:0 auto;padding:24px}
.head{
display:flex;justify-content:space-between;align-items:center;gap:16px;
padding:18px 20px;border:1px solid var(--line);border-radius:22px;background:rgba(13,27,42,.75)
}
.title{margin:0;font-size:24px}
.sub{margin:8px 0 0;color:var(--muted);font-size:13px;line-height:1.6}
.nav a{
text-decoration:none;color:var(--text);padding:10px 14px;border-radius:14px;
border:1px solid var(--line);background:rgba(255,255,255,.03);margin-left:10px;display:inline-block
}
.nav a.primary{background:linear-gradient(90deg, rgba(79,209,197,.18), rgba(96,165,250,.18))}
.grid{display:grid;gap:18px;margin-top:18px;grid-template-columns:repeat(12,1fr)}
.card{
grid-column:span 12;
border:1px solid var(--line);border-radius:22px;background:rgba(13,27,42,.75);
padding:18px;box-shadow:0 14px 40px rgba(0,0,0,.25)
}
.two{grid-column:span 12;display:grid;grid-template-columns:1fr 1fr;gap:18px}
.mini{
border:1px solid var(--line);border-radius:18px;padding:16px;background:rgba(255,255,255,.03)
}
.label{color:var(--muted);font-size:13px}
.value{font-size:24px;font-weight:800;margin-top:8px}
.value.ok{color:var(--ok)}
.table{width:100%;border-collapse:collapse;margin-top:10px;overflow:hidden;border-radius:18px}
.table th,.table td{
padding:12px 14px;border-bottom:1px solid var(--line);text-align:left;font-size:14px
}
.table th{color:#cfe2ff;background:rgba(255,255,255,.03)}
canvas{width:100%;height:320px;background:rgba(255,255,255,.02);border-radius:18px;border:1px solid var(--line)}
.note{color:var(--muted);font-size:13px;line-height:1.7}
@media (max-width:900px){.two{grid-template-columns:1fr}}
</style>
</head>
<body>
<div class="wrap">
<div class="head">
<div>
<h1 class="title">Maintenance Report</h1>
<p class="sub">Industrial machine diagnostic page for final-year preventive maintenance project.</p>
</div>
<div class="nav">
<a class="primary" href="/">Dashboard</a>
<a href="/report">Report</a>
<a href="/download">Download Data</a>
</div>
</div>
<div class="grid">
<div class="two">
<div class="mini">
<div class="label">Current Status</div>
<div class="value ok" id="statusText">NORMAL</div>
</div>
<div class="mini">
<div class="label">Health Score</div>
<div class="value" id="healthScore">100%</div>
</div>
</div>
<div class="card">
<h3 style="margin-top:0">Vibration History</h3>
<canvas id="chart" width="900" height="340"></canvas>
</div>
<div class="card">
<h3 style="margin-top:0">Recommended Maintenance Logic</h3>
<table class="table">
<tr><th>Condition</th><th>Meaning</th><th>Action</th></tr>
<tr><td>Low vibration</td><td>Machine running normally</td><td>No action required</td></tr>
<tr><td>Moderate vibration</td><td>Possible looseness or imbalance</td><td>Inspect mounting and alignment</td></tr>
<tr><td>High vibration</td><td>Machine may be faulty</td><td>Stop and check immediately</td></tr>
</table>
<p class="note" id="recommendation">Loading report...</p>
</div>
<div class="card">
<h3 style="margin-top:0">Project Summary</h3>
<p class="note">
This system uses an MPU6050 to sense machine vibration, simulated temperature/humidity values,
a potentiometer to control BO motor speed through an L298N driver, and an ESP32 web server
to display live industrial machine health data.
</p>
</div>
</div>
</div>
<script>
const chart = document.getElementById('chart');
const ctx = chart.getContext('2d');
let historyData = [];
function drawChart(data){
const w = chart.width, h = chart.height;
ctx.clearRect(0,0,w,h);
ctx.strokeStyle = 'rgba(255,255,255,0.08)';
for(let i=1;i<6;i++){
const y = (h/6)*i;
ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(w,y); ctx.stroke();
}
if(!data || data.length < 2){
ctx.fillStyle = 'rgba(255,255,255,0.65)';
ctx.font = '18px Segoe UI';
ctx.fillText('Waiting for data...', 24, 42);
return;
}
const maxV = Math.max(...data, 0.02);
const pad = 26;
const plotW = w - pad*2;
const plotH = h - pad*2;
ctx.strokeStyle = '#60a5fa';
ctx.lineWidth = 3;
ctx.beginPath();
data.forEach((v, i) => {
const x = pad + (i/(data.length-1))*plotW;
const y = pad + (1 - v/maxV)*plotH;
if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
});
ctx.stroke();
}
async function refresh(){
const d = await (await fetch('/api/data')).json();
document.getElementById('statusText').textContent = d.status;
document.getElementById('statusText').className = 'value ' + d.statusClass;
document.getElementById('healthScore').textContent = d.healthScore.toFixed(0) + '%';
document.getElementById('recommendation').textContent = d.recommendation;
historyData = await (await fetch('/api/history')).json();
drawChart(historyData);
}
refresh();
setInterval(refresh, 1000);
</script>
</body>
</html>
)rawliteral";
const char DOWNLOAD_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Download Data</title>
<style>
body{
margin:0;font-family:Segoe UI,Arial,sans-serif;
background:linear-gradient(180deg,#050b14,#08111d 60%,#050b14);
color:#e8eef7;
}
.wrap{max-width:900px;margin:0 auto;padding:24px}
.card{
margin-top:20px;padding:24px;border-radius:22px;
background:rgba(13,27,42,.78);border:1px solid rgba(255,255,255,.08);
box-shadow:0 18px 50px rgba(0,0,0,.3)
}
h1{margin:0 0 10px}
p{color:#93a4bd;line-height:1.6}
.btn{
display:inline-block;margin-top:12px;padding:12px 18px;border-radius:14px;
text-decoration:none;color:#fff;border:1px solid rgba(255,255,255,.08);
background:linear-gradient(90deg, rgba(79,209,197,.18), rgba(96,165,250,.18))
}
.row{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px}
.small{
color:#93a4bd;font-size:13px
}
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<h1>Download Machine Data</h1>
<p>
This page allows you to download the logged machine monitoring data as a CSV file.
The file opens directly in Microsoft Excel, Google Sheets, or LibreOffice Calc.
</p>
<div class="row">
<a class="btn" href="/downloadcsv">Download CSV File</a>
<a class="btn" href="/">Back to Dashboard</a>
</div>
<p class="small" style="margin-top:18px">
Logged fields: Time, Temperature, Humidity, Motor PWM, Vibration, Status
</p>
</div>
</div>
</body>
</html>
)rawliteral";
// ===================== HELPER FUNCTIONS =====================
String numOrNull(float value, int decimals = 2) {
if (isnan(value)) return "null";
return String(value, decimals);
}
String getStatusText(float vib) {
if (vib >= VIB_THRESHOLD_CRITICAL) return "CRITICAL";
if (vib >= VIB_THRESHOLD_WARNING) return "WARNING";
return "NORMAL";
}
String getStatusClass(float vib) {
if (vib >= VIB_THRESHOLD_CRITICAL) return "danger";
if (vib >= VIB_THRESHOLD_WARNING) return "warn";
return "ok";
}
String getRecommendation(float vib) {
if (vib >= VIB_THRESHOLD_CRITICAL) {
return "Critical vibration detected. Stop the machine and inspect bearings, mounting bolts, alignment, and rotating parts immediately.";
} else if (vib >= VIB_THRESHOLD_WARNING) {
return "Warning level vibration detected. Schedule inspection soon and check for looseness or imbalance.";
}
return "Machine condition is normal. Continue monitoring for predictive maintenance.";
}
float getHealthScore(float vib) {
float score = 100.0 - (vib * 500.0);
if (score < 0) score = 0;
if (score > 100) score = 100;
return score;
}
// ===================== SIMULATED ENVIRONMENT =====================
void generateFakeEnvironment() {
temperature = random(320, 341) / 10.0; // 32.0 to 34.0 °C
float baseHumidity = 72.0 - ((temperature - 32.0) * 7.0);
humidity = baseHumidity + (random(-5, 6) / 10.0);
if (humidity < 55.0) humidity = 55.0;
if (humidity > 80.0) humidity = 80.0;
}
// ===================== MOTOR CONTROL =====================
void setMotorSpeed(int pwmValue) {
if (pwmValue <= 0) {
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
ledcWrite(ENA, 0);
} else {
digitalWrite(IN1, HIGH);
digitalWrite(IN2, LOW);
ledcWrite(ENA, pwmValue);
}
}
// ===================== BUZZER CONTROL =====================
void updateBuzzer(bool critical) {
if (!critical) {
digitalWrite(BUZZER_PIN, LOW);
buzzerState = false;
return;
}
unsigned long now = millis();
unsigned long interval = buzzerState ? BUZZER_ON_TIME : BUZZER_OFF_TIME;
if (now - lastBuzzerToggle >= interval) {
buzzerState = !buzzerState;
digitalWrite(BUZZER_PIN, buzzerState ? HIGH : LOW);
lastBuzzerToggle = now;
}
}
// ===================== MPU6050 READ =====================
void readMPU6050() {
Wire.beginTransmission(MPU_ADDR);
Wire.write(0x3B);
if (Wire.endTransmission(false) != 0) return;
if (Wire.requestFrom(MPU_ADDR, 6, true) < 6) return;
ax = (Wire.read() << 8) | Wire.read();
ay = (Wire.read() << 8) | Wire.read();
az = (Wire.read() << 8) | Wire.read();
float axf = ax / 16384.0;
float ayf = ay / 16384.0;
float azf = az / 16384.0;
float magnitude = sqrt(axf * axf + ayf * ayf + azf * azf);
vibration = fabs(magnitude - prevMag);
prevMag = magnitude;
}
// ===================== HISTORY + CSV =====================
void updateHistory() {
vibHistory[historyIndex] = vibration;
historyIndex = (historyIndex + 1) % HISTORY_SIZE;
}
void logCSVData() {
unsigned long currentTime = millis() / 1000;
String status = getStatusText(vibration);
csvData += String(currentTime) + ",";
csvData += String(temperature, 1) + ",";
csvData += String(humidity, 1) + ",";
csvData += String(motorPWM) + ",";
csvData += String(vibration, 4) + ",";
csvData += status + "\n";
// Keep memory under control
if (csvData.length() > 20000) {
// Reset header and keep latest data only if log gets too large
csvData = "Time,Temperature,Humidity,MotorPWM,Vibration,Status\n";
}
}
// ===================== LCD UPDATE =====================
void updateLCD() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("T:");
lcd.print(temperature, 1);
lcd.print(" H:");
lcd.print(humidity, 0);
lcd.setCursor(0, 1);
lcd.print("M:");
lcd.print(motorPWM);
lcd.print(" V:");
lcd.print(vibration, 2);
if (vibration >= VIB_THRESHOLD_CRITICAL) {
lcd.print(" C");
} else if (vibration >= VIB_THRESHOLD_WARNING) {
lcd.print(" W");
} else {
lcd.print(" N");
}
}
// ===================== WEB HANDLERS =====================
void handleRoot() {
server.send_P(200, "text/html", DASHBOARD_HTML);
}
void handleReport() {
server.send_P(200, "text/html", REPORT_HTML);
}
void handleDownloadPage() {
server.send_P(200, "text/html", DOWNLOAD_HTML);
}
void handleApiData() {
String json = "{";
json += "\"temperature\":" + numOrNull(temperature, 1) + ",";
json += "\"humidity\":" + numOrNull(humidity, 0) + ",";
json += "\"motorPWM\":" + String(motorPWM) + ",";
json += "\"vibration\":" + String(vibration, 4) + ",";
json += "\"healthScore\":" + String(getHealthScore(vibration), 1) + ",";
json += "\"status\":\"" + getStatusText(vibration) + "\",";
json += "\"statusClass\":\"" + getStatusClass(vibration) + "\",";
json += "\"recommendation\":\"" + getRecommendation(vibration) + "\"";
json += "}";
server.send(200, "application/json", json);
}
void handleApiHistory() {
String json = "[";
for (int i = 0; i < HISTORY_SIZE; i++) {
int idx = (historyIndex + i) % HISTORY_SIZE;
json += String(vibHistory[idx], 4);
if (i < HISTORY_SIZE - 1) json += ",";
}
json += "]";
server.send(200, "application/json", json);
}
void handleDownloadCSV() {
server.sendHeader("Content-Type", "text/csv");
server.sendHeader("Content-Disposition", "attachment; filename=vibration_data.csv");
server.send(200, "text/csv", csvData);
}
// ===================== SETUP =====================
void setup() {
Serial.begin(115200);
delay(1000);
randomSeed(esp_random());
startMillis = millis();
// I2C
Wire.begin(SDA_PIN, SCL_PIN);
Wire.setClock(100000);
// LCD
lcd.init();
lcd.backlight();
// Motor driver pins
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
// Buzzer pin
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
// PWM attach
ledcAttach(ENA, PWM_FREQ, PWM_RESOLUTION);
// Potentiometer
pinMode(POT_PIN, INPUT);
// Init history
for (int i = 0; i < HISTORY_SIZE; i++) vibHistory[i] = 0;
// Wake MPU6050
Wire.beginTransmission(MPU_ADDR);
Wire.write(0x6B);
Wire.write(0x00);
if (Wire.endTransmission() != 0) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("MPU NOT FOUND");
Serial.println("MPU6050 NOT FOUND");
while (1);
}
generateFakeEnvironment();
// Wi-Fi AP
WiFi.mode(WIFI_AP);
WiFi.softAP(AP_SSID, AP_PASS);
IPAddress ip = WiFi.softAPIP();
Serial.println("================================");
Serial.println("AI PREVENTIVE MAINTENANCE START");
Serial.print("AP IP: ");
Serial.println(ip);
Serial.println("Connect to WiFi: AI_Machine_Monitor");
Serial.println("Password: 12345678");
Serial.println("Open: http://192.168.4.1");
Serial.println("================================");
// Web routes
server.on("/", HTTP_GET, handleRoot);
server.on("/report", HTTP_GET, handleReport);
server.on("/download", HTTP_GET, handleDownloadPage);
server.on("/downloadcsv", HTTP_GET, handleDownloadCSV);
server.on("/api/data", HTTP_GET, handleApiData);
server.on("/api/history", HTTP_GET, handleApiHistory);
server.begin();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SYSTEM READY");
lcd.setCursor(0, 1);
lcd.print("192.168.4.1");
delay(2000);
}
// ===================== LOOP =====================
void loop() {
server.handleClient();
unsigned long now = millis();
// Potentiometer -> motor speed
potValue = analogRead(POT_PIN);
motorPWM = map(potValue, 0, 4095, 0, 255);
if (motorPWM < 20) motorPWM = 0;
setMotorSpeed(motorPWM);
// Read MPU6050 faster
if (now - lastMpuRead >= 150) {
readMPU6050();
lastMpuRead = now;
}
// Fake environment values every 2 seconds
if (now - lastEnvRead >= 2000) {
generateFakeEnvironment();
lastEnvRead = now;
}
// Update vibration history and CSV every 1 second
if (now - lastHistoryUpdate >= 1000) {
updateHistory();
logCSVData();
lastHistoryUpdate = now;
}
// Buzzer only on critical vibration
updateBuzzer(vibration >= VIB_THRESHOLD_CRITICAL);
// LCD refresh
if (now - lastLcdUpdate >= 400) {
updateLCD();
lastLcdUpdate = now;
}
// Serial monitor
Serial.println("--------------------------");
Serial.print("Temp: ");
Serial.print(temperature);
Serial.print(" C Hum: ");
Serial.print(humidity);
Serial.println(" %");
Serial.print("Motor PWM: ");
Serial.println(motorPWM);
Serial.print("Vibration: ");
Serial.println(vibration, 4);
Serial.print("Status: ");
Serial.println(getStatusText(vibration));
}
🌐 Web Dashboard Features
The web server makes the project look more professional and industrial.
The dashboard shows:
Live temperature
Live humidity
Motor speed
Vibration value
Machine status
Health score
Maintenance recommendation
Vibration trend graph
There is also a separate page for downloading the machine data as a CSV file.
This makes the project suitable for:
Final year project demonstration
Industrial automation showcase
Predictive maintenance concept display
Excel-based data analysis
💾 CSV / Excel Download Feature
One of the most useful features in this project is the data download option.
The ESP32 logs machine data in CSV format and provides a download page through the web server. This CSV file can be opened directly in Excel or Google Sheets for further analysis.
The logged data includes:
Time
Temperature
Humidity
Motor PWM
Vibration
Machine status
This makes the project more valuable because the data can be stored, reviewed, and analyzed later.
🧠 How the Code Works
1. Sensor Reading
The MPU6050 is used to measure vibration changes from the machine body.
The vibration value is calculated from acceleration magnitude differences.
2. Motor Control
The potentiometer changes the PWM value sent to the L298N driver.
This changes the speed of the BO motor.
3. Health Monitoring
The system compares vibration values with predefined thresholds:
Normal
Warning
Critical
If the vibration is too high, the system treats it as a maintenance alert.
4. LCD Output
The LCD continuously displays the most important machine values.
5. Buzzer Alert
When the vibration reaches the critical level, the buzzer beeps to warn the user.
6. Web Dashboard
The ESP32 web server displays live values and allows CSV download.
📊 Data Logging Logic
The system records data every second.
Each record contains:
Time
Temperature
Humidity
Motor speed
Vibration
Status
This data is stored in CSV format so that it can be downloaded later for analysis.
🛠️ Build Process
First, I connected the MPU6050, potentiometer, buzzer, LCD, and motor driver to the ESP32.
Next, I uploaded the code and checked whether the LCD, motor, and vibration readings were working correctly.
Then, I opened the ESP32 web dashboard using the hotspot IP address.
After that, I tested the motor speed control using the potentiometer.
Finally, I verified that the buzzer triggered properly when vibration crossed the critical limit.
🎮 Control / Display Features
Real-time vibration detection
Motor speed control using potentiometer
LCD display output
Buzzer alert on critical vibration
Web dashboard monitoring
CSV data download for Excel
📌 Important Notes
The vibration threshold may need adjustment depending on your motor setup.
The BO motor should be mounted firmly to get proper vibration readings.
The CSV feature helps in storing maintenance data for later review.
The project can be expanded further with cloud monitoring, IoT alerts, or machine learning analysis.
🎉 Final Result
✅ AI preventive maintenance system built successfully
✅ ESP32 reading machine vibration in real time
✅ BO motor speed controlled through potentiometer
✅ LCD showing live monitoring values
✅ Buzzer alert working on critical vibration
✅ Web dashboard working properly
✅ CSV download available for Excel analysis
This project is a strong example of combining embedded systems, industrial monitoring, and smart maintenance logic into one practical final-year project.
❤️ Let’s Connect
This project is useful for learning:
ESP32 programming
I2C communication
Vibration monitoring
Motor control
Web server development
Predictive maintenance basics
CSV data logging
Industrial machine health tracking
You can use this as a blog post, mini project report, or project showcase content for LearnWithDTrend.
Super
ReplyDelete