VAIBHAV DUBEY
Published © GPL3+

Smart Structural Road Health Monitoring System

An AI-powered Structural Health Monitoring system that tracks real-time vibrations, temp, and environmental stress to assess bridge health.

AdvancedFull instructions providedOver 4 days268
Smart Structural Road Health Monitoring System

Things used in this project

Hardware components

GRAVITY DHT11 WITH CABLE DFRobot
×1
6 DOF Sensor - MPU6050
DFRobot 6 DOF Sensor - MPU6050
×1
ESP32-C6-DEVKITC-1-N8 BOARD DFRobot
×1

Software apps and online services

Arduino IDE
Arduino IDE
VS Code
Microsoft VS Code

Story

Read more

Schematics

schematic_11302025230909_VP33ArxWyp.jpeg

Code

Dashboard Development Platform: VS Code

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Bridge Health Monitoring Dashboard</title>

  <!-- Three.js + OrbitControls -->
  <script src="https://cdn.jsdelivr.net/npm/three@0.128/build/three.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.128/examples/js/controls/OrbitControls.js"></script>

  <!-- Chart.js -->
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: 'Segoe UI', sans-serif; background: #0a0a0a; color: #fff; overflow: hidden; }

    #container { display: flex; height: 100vh; width: 100vw; }

    #bim-view {
      flex: 2;
      position: relative;
      min-width: 400px;
      min-height: 400px;
    }

    #sidebar {
      flex: 1;
      background: #141414;
      padding: 20px;
      overflow-y: auto;
      border-left: 1px solid #222;
    }

    h2 { color: #00ffcc; margin-bottom: 10px; }
    .status-card {
      background: linear-gradient(135deg, #1e1e1e, #2a2a2a);
      border-radius: 12px;
      padding: 20px;
      margin-bottom: 20px;
      box-shadow: 0 4px 20px rgba(0,0,0,0.3);
    }

    .health-state { font-size: 2em; font-weight: bold; margin-bottom: 10px; }
    .metric { margin: 8px 0; font-size: 1.05em; }
    .metric span { color: #00ffcc; font-weight: bold; }

    .risk-meter {
      height: 25px;
      background: #1a1a1a;
      border-radius: 15px;
      overflow: hidden;
      margin-top: 10px;
    }
    .risk-fill {
      height: 100%;
      background: linear-gradient(90deg, #00ff88, #ffff00, #ff4444);
      width: 10%;
      border-radius: 15px;
      transition: width 0.6s ease;
    }

    /* Gauges */
    #gauges {
      position: absolute;
      top: 15px;
      left: 15px;
      display: flex;
      gap: 20px;
      z-index: 9999;
    }

    #gauges canvas {
      background: #111;
      border-radius: 50%;
      padding: 8px;
      box-shadow: 0 0 12px rgba(0,255,255,0.3);
    }
  </style>
</head>

<body>

<!-- GAUGES -->
<div id="gauges">
  <canvas id="thermalGauge" width="140" height="140"></canvas>
  <canvas id="corrosionGauge" width="140" height="140"></canvas>
</div>

<div id="container">
  <div id="bim-view"></div>

  <div id="sidebar">

    <div class="status-card">
      <h2>Bridge Health Status</h2>
      <div class="health-state" id="health-state">LOADING...</div>

      <div class="metric">Condition: <span id="cond">--</span></div>
      <div class="metric">Degradation Score: <span id="degradation">--</span></div>
      <div class="metric">Forecast (Next 30d): <span id="forecast">--</span></div>
      <div class="metric">Confidence: <span id="confidence">--</span></div>

      <h3 style="margin-top:10px; color:#00ffcc;">Risk Meter</h3>
      <div class="risk-meter"><div class="risk-fill" id="risk-fill"></div></div>
    </div>

    <div class="status-card">
      <h2>Forecast Trends</h2>
      <canvas id="trendChart"></canvas>
    </div>

    <div class="status-card">
      <h2>Vibration Waveform</h2>
      <canvas id="waveformChart" height="120"></canvas>
    </div>

    <div class="status-card">
      <h2>API Stats</h2>
      <div class="metric">Total Samples: <span id="total-samples">--</span></div>
      <div class="metric">Latest Ping: <span id="ping">--</span></div>
    </div>

  </div>
</div>

<script>
/*API URLs*/
const API_LATEST = "http://SAME_IP_AS_ON_ARDUINO_IDE:8000/latest";
const API_STATS  = "http://SAME_IP_AS_ON_ARDUINO_IDE:8000/stats";

/* 3D SETUP */
const container = document.getElementById('bim-view');
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a0a);

const camera = new THREE.PerspectiveCamera(
  75,
  container.clientWidth / container.clientHeight,
  0.1,
  1000
);
camera.position.set(8, 4, 8);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild(renderer.domElement);

window.addEventListener("resize", () => {
  renderer.setSize(container.clientWidth, container.clientHeight);
  camera.aspect = container.clientWidth / container.clientHeight;
  camera.updateProjectionMatrix();
});

/* Orbit Controls */
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.target.set(0, 1.5, 0);

/* Bridge Model */
const pillarGeo = new THREE.CylinderGeometry(0.3, 0.3, 3, 32);
const pillarMat = new THREE.MeshPhongMaterial({ color: "#555" });

const p1 = new THREE.Mesh(pillarGeo, pillarMat);
p1.position.set(-3, 0, 0);

const p2 = new THREE.Mesh(pillarGeo, pillarMat);
p2.position.set(3, 0, 0);

const bridgeGeo = new THREE.BoxGeometry(8, 0.4, 1.2);
const bridgeMat = new THREE.MeshPhongMaterial({ color: "#00ff88" });
const bridge = new THREE.Mesh(bridgeGeo, bridgeMat);
bridge.position.y = 1.75;

const group = new THREE.Group();
group.add(p1); group.add(p2); group.add(bridge);
scene.add(group);

scene.add(new THREE.AmbientLight(0xffffff, 0.4));
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);

/* CONDITION COLOR */
function applyConditionColor(condition) {
  if (condition === "normal") {
    bridge.material.color.set("#00ff88");
  } else if (condition === "minor") {
    bridge.material.color.set("#ffff00");
  } else if (condition === "moderate") {
    bridge.material.color.set("#ff8800");
  } else if (condition === "severe") {
    bridge.material.color.set("#ff0000");
  }
}

/* THERMAL HEATMAP */
function applyThermalColor(thermalIndex) {
  const t = Math.min(1, Math.max(0, (thermalIndex + 10) / 30));
  let heat = new THREE.Color();

  if (t < 0.3) heat.setRGB(0, t/0.3, 1);
  else if (t < 0.6) heat.setRGB((t-0.3)/0.3, 1, 0);
  else heat.setRGB(1, 1-(t-0.6)/0.4, 0);

  bridge.material.color.lerp(heat, 0.08);   //  small tint only
  p1.material.color.set("#555");
  p2.material.color.set("#555");
}

/* Animate */
function animate() {
  requestAnimationFrame(animate);
  controls.update();
  renderer.render(scene, camera);
}
animate();

/* Gauges */
function drawGauge(ctx, val, min, max, label, color) {
  const W = ctx.canvas.width;
  const c = W / 2;
  const r = W * 0.38;
  ctx.clearRect(0, 0, W, W);

  ctx.beginPath();
  ctx.strokeStyle = "#333";
  ctx.lineWidth = 12;
  ctx.arc(c, c, r, Math.PI, 0);
  ctx.stroke();

  const pct = (val - min) / (max - min);
  const angle = Math.PI + pct * Math.PI;

  ctx.beginPath();
  ctx.strokeStyle = color;
  ctx.lineWidth = 12;
  ctx.arc(c, c, r, Math.PI, angle);
  ctx.stroke();

  ctx.beginPath();
  ctx.strokeStyle = "white";
  ctx.moveTo(c, c);
  ctx.lineTo(c + Math.cos(angle)*r, c + Math.sin(angle)*r);
  ctx.stroke();

  ctx.fillStyle = "#00ffcc";
  ctx.textAlign = "center";
  ctx.font = "14px Segoe UI";
  ctx.fillText(label, c, c + 40);
}

const thermalCtx = document.getElementById("thermalGauge").getContext("2d");
const corrosionCtx = document.getElementById("corrosionGauge").getContext("2d");

/* Charts */
const trendChart = new Chart(document.getElementById("trendChart"), {
  type: "line",
  data: { labels: [], datasets: [
    { label: "Degradation", borderColor: "#ff9900", data: [], tension: 0.3 },
    { label: "Forecast", borderColor: "#00ffcc", data: [], tension: 0.3 }
  ]},
  options: { animation: false }
});

const waveformChart = new Chart(document.getElementById("waveformChart"), {
  type: "line",
  data: { labels: [], datasets: [{
    label: "Vibration",
    borderColor: "#00ccff",
    backgroundColor: "rgba(0,200,255,0.15)",
    pointRadius: 0,
    borderWidth: 2,
    tension: 0.25,
    data: []
  }]},
  options: {
    animation: false,
    plugins: { legend: { display: false }},
    scales: {
      x: { display: false },
      y: { ticks: { color: "#aaa" } }
    }
  }
});

/* API Refresh */
async function refresh() {
  try {
    const latest = await fetch(API_LATEST).then(r => r.json());
    const stats  = await fetch(API_STATS).then(r => r.json());

    if (!latest.condition) return;

    /* Set condition UI */
    document.getElementById("health-state").textContent = latest.condition.toUpperCase();
    document.getElementById("cond").textContent = latest.condition;
    document.getElementById("degradation").textContent = latest.degradation_score;
    document.getElementById("forecast").textContent = latest.forecast_30d;
    document.getElementById("confidence").textContent = latest.confidence;

    document.getElementById("risk-fill").style.width = (latest.degradation_score * 100) + "%";

    /* Apply bridge colors */
    applyConditionColor(latest.condition);
    applyThermalColor(latest.thermal_index);

    /* Gauges */
    drawGauge(thermalCtx, latest.thermal_index, -10, 20, `Thermal Dev ${latest.thermal_index}°C`, "#00ccff");

    let corrVal = latest.corrosion_risk === "moderate" ? 60 :
                  latest.corrosion_risk === "high" ? 90 : 20;
    let corrColor = latest.corrosion_risk === "high" ? "#ff4444" :
                    latest.corrosion_risk === "moderate" ? "#ffff00" : "#00ff88";

    drawGauge(corrosionCtx, corrVal, 0, 100, `Corrosion Risk: ${latest.corrosion_risk}`, corrColor);

    /* Trend Chart */
    trendChart.data.labels.push("");
    trendChart.data.datasets[0].data.push(latest.degradation_score);
    trendChart.data.datasets[1].data.push(latest.forecast_30d);

    if (trendChart.data.labels.length > 20) {
      trendChart.data.labels.shift();
      trendChart.data.datasets[0].data.shift();
      trendChart.data.datasets[1].data.shift();
    }
    trendChart.update();

    /* Vibration waveform */
    const mag = Math.sqrt(latest.ax**2 + latest.ay**2 + latest.az**2);
    waveformChart.data.labels.push("");
    waveformChart.data.datasets[0].data.push(mag);

    if (waveformChart.data.labels.length > 40) {
      waveformChart.data.labels.shift();
      waveformChart.data.datasets[0].data.shift();
    }
    waveformChart.update();

    /* Stats */
    document.getElementById("total-samples").textContent = stats.samples_total;
    document.getElementById("ping").textContent = Date.now();

  } catch (err) {
    console.log("Error:", err);
  }
}

setInterval(refresh, 1000);
refresh();
</script>

</body>
</html>

Credits

VAIBHAV DUBEY
10 projects • 6 followers
Aspiring hardware engineer focused on embedded systems, high‑reliability circuit design, and PCB layout for IoT and robotics projects.

Comments