#include <WiFi.h>
// --- Hardware Setup ---
const int VIB_PIN = 26;
const int CURR_PIN = 27;
const int LED_PIN = 15;
// --- Wi-Fi Setup ---
const char* ssid = "Wokwi-GUEST";
const char* password = "";
WiFiServer server(80); // Create a web server on port 80
// --- Analytics Variables ---
const int WINDOW_SIZE = 10;
float health_history[WINDOW_SIZE];
int history_index = 0;
int history_count = 0;
const float CRITICAL_THRESHOLD = 2.0;
// Global variables for the web server to access
int current_vib = 0;
int current_load = 10;
float current_avg_hi = 0.0;
String current_status = "OPTIMAL";
// Timer for non-blocking sensor reads
unsigned long previousMillis = 0;
const long interval = 500; // Read sensors every 500ms
// ==============================================================================
// THE DASHBOARD & GRAPH (Stored directly in the Pico's memory)
// ==============================================================================
const char index_html[] PROGMEM = R"=====(
<!DOCTYPE html>
<html>
<head>
<title>Edge AI Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { background-color: #121212; color: #ffffff; font-family: 'Segoe UI', sans-serif; text-align: center; padding: 20px; }
.container { background-color: #1e1e1e; padding: 30px; border-radius: 12px; box-shadow: 0 4px 8px rgba(0,0,0,0.5); max-width: 800px; margin: auto; }
h1 { margin-top: 0; color: #fff; border-bottom: 2px solid #333; padding-bottom: 10px;}
.metrics { display: flex; justify-content: space-around; font-size: 20px; margin-bottom: 20px; background: #2c2c2c; padding: 15px; border-radius: 8px;}
.status-badge { display: inline-block; padding: 10px 20px; border-radius: 20px; font-weight: bold; font-size: 24px; margin-bottom: 20px; background-color: #4CAF50; color: white;}
.warning { background-color: #FF9800 !important; color: black !important; }
.critical { background-color: #F44336 !important; animation: blink 1s linear infinite; }
@keyframes blink { 50% { opacity: 0; } }
canvas { background-color: #2c2c2c; border-radius: 8px; padding: 10px; }
</style>
</head>
<body>
<div class="container">
<h1>Predictive Maintenance Node</h1>
<div class="status-badge" id="statusBadge">Status: OPTIMAL</div>
<div class="metrics">
<div>Load: <strong id="load" style="color:#2196F3">0</strong></div>
<div>Vibration: <strong id="vib" style="color:#FF9800">0</strong></div>
<div>Health Ratio: <strong id="hi" style="color:#4CAF50">0.00</strong></div>
</div>
<canvas id="healthChart"></canvas>
</div>
<script>
// Initialize the Chart
const ctx = document.getElementById('healthChart').getContext('2d');
const healthChart = new Chart(ctx, {
type: 'line',
data: {
labels: [], // Time labels
datasets: [{
label: 'Motor Health Ratio',
borderColor: '#4CAF50',
backgroundColor: 'rgba(76, 175, 80, 0.2)',
borderWidth: 3,
data: [], // Data points
fill: true,
tension: 0.4
}]
},
options: {
animation: false,
scales: { y: { beginAtZero: true, max: 3 } }
}
});
// Fetch data from the Pico W every 500ms
setInterval(() => {
fetch('/data').then(response => response.json()).then(data => {
// Update Text
document.getElementById('vib').innerText = data.vibration;
document.getElementById('load').innerText = data.load;
document.getElementById('hi').innerText = data.health_index.toFixed(2);
// Update Status & Colors
const badge = document.getElementById('statusBadge');
badge.innerText = "Status: " + data.status;
badge.className = 'status-badge'; // Reset
healthChart.data.datasets[0].borderColor = '#4CAF50';
if (data.status === 'WARNING') {
badge.classList.add('warning');
healthChart.data.datasets[0].borderColor = '#FF9800';
} else if (data.status === 'CRITICAL') {
badge.classList.add('critical');
healthChart.data.datasets[0].borderColor = '#F44336';
}
// Update Graph
const now = new Date();
healthChart.data.labels.push(now.getSeconds() + "s");
healthChart.data.datasets[0].data.push(data.health_index);
// Keep only the last 20 points on the graph
if (healthChart.data.labels.length > 20) {
healthChart.data.labels.shift();
healthChart.data.datasets[0].data.shift();
}
healthChart.update();
});
}, 500);
</script>
</body>
</html>
)=====";
// ==============================================================================
// SETUP & MAIN LOOP
// ==============================================================================
void setup() {
Serial1.begin(115200);
analogReadResolution(12);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
Serial1.println("Connecting to WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500); Serial1.print(".");
}
Serial1.println("\nWiFi connected!");
server.begin();
Serial1.println("Web Server Started.");
}
void loop() {
// 1. NON-BLOCKING SENSOR READS
// This allows the web server to run smoothly without freezing
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
current_vib = analogRead(VIB_PIN);
current_load = analogRead(CURR_PIN);
if (current_load < 10) current_load = 10;
float current_hi = (float)current_vib / (float)current_load;
health_history[history_index] = current_hi;
history_index = (history_index + 1) % WINDOW_SIZE;
if (history_count < WINDOW_SIZE) history_count++;
float sum = 0;
for (int i = 0; i < history_count; i++) sum += health_history[i];
current_avg_hi = sum / history_count;
if (current_avg_hi > CRITICAL_THRESHOLD) {
current_status = "CRITICAL";
digitalWrite(LED_PIN, HIGH);
} else if (current_avg_hi > 1.2) {
current_status = "WARNING";
digitalWrite(LED_PIN, LOW);
} else {
current_status = "OPTIMAL";
digitalWrite(LED_PIN, LOW);
}
}
// 2. HANDLE INCOMING WEB DASHBOARD REQUESTS
WiFiClient client = server.available();
if (client) {
String request = client.readStringUntil('\r');
client.flush();
// If the Javascript asks for data, send a JSON string
if (request.indexOf("GET /data") != -1) {
String json = "{\"vibration\":" + String(current_vib) +
",\"load\":" + String(current_load) +
",\"health_index\":" + String(current_avg_hi, 2) +
",\"status\":\"" + current_status + "\"}";
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: application/json");
client.println("Connection: close");
client.println();
client.println(json);
}
// Otherwise, send the main HTML Dashboard
else {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
client.println(index_html);
}
client.stop();
}
}