🔧 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

ComponentQuantity
ESP32 DevKit1
MPU6050 Sensor1
Potentiometer1
L298N Motor Driver1
BO Motor1
I2C LCD Display 16x21
Active Buzzer1
Jumper WiresAs needed
Breadboard1
USB Cable1
External Motor Power Supply1

🔌 Connections

ESP32 Pin Configuration

ComponentESP32 Pin
MPU6050 SDAGPIO 21
MPU6050 SCLGPIO 22
Potentiometer Middle PinGPIO 34
L298N IN1GPIO 26
L298N IN2GPIO 27
L298N ENAGPIO 25
BuzzerGPIO 32
LCD SDAGPIO 21
LCD SCLGPIO 22
LCD VCC5V
LCD GNDGND

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.

Comments

Post a Comment

Popular posts from this blog

🎯 Build Your Own RFID Attendance System Using Arduino + ESP32 + Google Sheets

How to make a Rfid id tag to turn on the Motor

Build Your Own Timer Plug – Automatic Mobile Charger Cut-Off Using ESP32 (Hotspot Mode)