Practice Lab Guide

Project 9: Edge AI / TinyML Anomaly Detector

Deploy a real-time Z-score statistical classification model directly on an ESP32 to detect sensor anomalies.
Domain
Edge AI / Signal Processing
Difficulty
⭐⭐⭐⭐☆ (Advanced)
Course Module
Edge AI & TinyML
Deliverables
Edge Classifier Firmware, Testing Dashboard
1. Edge AI Classification & Buffer Topology

Cloud-based anomaly detection introduces latency and requires continuous network access. To run diagnostics locally, we implement an Edge AI pipeline on the ESP32. The diagram below details the processing flow. The firmware stores a rolling buffer of the last 10 sensor readings in RAM. For each new reading, the CPU calculates the mean and standard deviation of the window, then computes the Z-score. If the value falls outside the threshold (Z > 2.5), the ESP32 flags an anomaly and activates a buzzer alert.

Sensor Stream Reading: 24.8 °C Reading: 24.6 °C Reading: 24.5 °C Sudden Spike: 31.0°C ESP32 ROLLING WINDOW BUFFER Mean (μ) = Σ x_i / N StdDev (σ) = sqrt(Σ(x_i - μ)² / N) Z-Score Classification Formula Z = |x - μ| / σ Classifies values where Z > 2.5 as anomalies Actuator Output BUZZER LED
2. Part 1: Step-by-Step Individual Code Implementation & Verification Steps

Follow these detailed steps to set up the Edge AI classification workspace inside your VirtualBox VM, compile the C++ firmware, and verify detection logs.

STEP 1

Launch VM Terminal and Create Edge AI Folder

Boot up your VirtualBox Ubuntu machine. Open the terminal (Ctrl+Alt+T) and create a directory to organize your C++ and Python source files.

ubuntu@iot-vm:~$ mkdir -p ~/workspace/edge_ml && cd ~/workspace/edge_ml
We run `mkdir -p` and `cd` to generate and enter a dedicated workspace directory named `~/workspace/edge_ml` to isolate our Edge AI testing scripts.
STEP 2

Implement Rolling Window Buffer in Firmware

Declare a rolling window array in your C++ sketch to store the latest sensor readings.

C++ Code snippet to write: #define WINDOW_SIZE 10 float window[WINDOW_SIZE]; int windowIndex = 0; bool bufferFull = false; void insertValue(float value) { window[windowIndex] = value; windowIndex = (windowIndex + 1) % WINDOW_SIZE; if (windowIndex == 0) bufferFull = true; }
We implement a circular buffer in C++ using modulo division to continuously update the array with the latest sensor values.
STEP 3

Implement Mean & Standard Deviation Calculations

Add functions to calculate the statistical mean and standard deviation of the current buffer window.

C++ Code snippet to write: float getMean() { float sum = 0; for (int i = 0; i < WINDOW_SIZE; i++) sum += window[i]; return sum / WINDOW_SIZE; } float getStdDev(float mean) { float sumSqDiff = 0; for (int i = 0; i < WINDOW_SIZE; i++) { sumSqDiff += pow(window[i] - mean, 2); } return sqrt(sumSqDiff / WINDOW_SIZE); }
We add functions to compute the statistical mean and standard deviation, which are required to evaluate the Z-score of new readings.
STEP 4

Implement Z-Score Classification Logic

Compare new sensor readings against the rolling window's historical data, flagging anomalies if the Z-score exceeds 2.5.

C++ Code snippet to write: bool isAnomaly(float value, float mean, float stdDev) { if (stdDev < 0.1) return false; // Prevent division by zero float zScore = abs(value - mean) / stdDev; Serial.printf("Value: %.2f | Mean: %.2f | StdDev: %.2f | Z: %.2f\n", value, mean, stdDev, zScore); return zScore > 2.5; }
We implement the classification logic, using a threshold check to flag readings that deviate significantly from the rolling historical average.
STEP 5

Compile and Upload the Firmware

Upload the completed firmware to your ESP32 using the Arduino IDE. Keep the USB serial connection active.

IDE GUI Operations: 1. Open the Arduino IDE. 2. Select your target port ("/dev/ttyUSB0" on Linux VM or "COM3" on Windows). 3. Click the circular Upload arrow icon in the toolbar. 4. Once uploaded, go to "Tools" -> "Serial Monitor". Configure the baud rate to 115200.
We flash the firmware to run the statistical anomaly detection loop on the ESP32, and open the serial monitor to view real-time calculations.
STEP 6

Test Anomaly Detection with a Temperature Spike

Warm the temperature sensor (e.g. by breathing on it or pressing it with your finger) to simulate a sudden environmental spike. The onboard LED should light up immediately.

Serial Monitor logs output: Value: 24.50 | Mean: 24.45 | StdDev: 0.12 | Z: 0.42 Value: 24.60 | Mean: 24.48 | StdDev: 0.14 | Z: 0.85 Value: 31.00 | Mean: 24.52 | StdDev: 0.18 | Z: 36.00 [ALERT] ANOMALY DETECTED! Triggering buzzer and warning LED.
We warm the sensor to verify that sudden readings trigger the Z-score alert threshold, lighting up the warning LED.
3. Edge AI Processing Flowchart

The processing flowchart below illustrates the logic path executed on the ESP32 for every incoming sensor measurement.

New Reading Is Buffer Full? (Needs 10 values) Yes No (Fill Buffer) Calculate Z-Score Z = |x - μ| / σ Z > 2.5? Yes (Anomaly) ACTIVATE ALERT No Idle
4. Part 2: Complete Edge AI Firmware Code

Copy the code below to implement the rolling-window Z-score anomaly detector on your ESP32. The script processes sensor values and toggles alert pins.

// Practice Project 9: Edge AI / TinyML Anomaly Detector Firmware #include <Arduino.h> const int ALERT_LED = 2; // Onboard LED const int BUZZER_PIN = 13; // Piezo buzzer warning pin const int SENSOR_PIN = 34; // Analog sensor input (LDR or simulated potentiometer) #define WINDOW_SIZE 10 // Rolling window size float window[WINDOW_SIZE]; int windowIndex = 0; bool bufferFull = false; void insertValue(float val) { window[windowIndex] = val; windowIndex = (windowIndex + 1) % WINDOW_SIZE; if (windowIndex == 0) { bufferFull = true; } } float getMean() { float sum = 0; for (int i = 0; i < WINDOW_SIZE; i++) { sum += window[i]; } return sum / (float)WINDOW_SIZE; } float getStdDev(float mean) { float sumSqDiff = 0; for (int i = 0; i < WINDOW_SIZE; i++) { sumSqDiff += pow(window[i] - mean, 2); } return sqrt(sumSqDiff / (float)WINDOW_SIZE); } void setup() { pinMode(ALERT_LED, OUTPUT); pinMode(BUZZER_PIN, OUTPUT); digitalWrite(ALERT_LED, LOW); digitalWrite(BUZZER_PIN, LOW); Serial.begin(115200); Serial.println("Edge AI Anomaly Detector initialized."); Serial.println("Collecting initial window buffer values..."); } void loop() { // Read analog sensor value float reading = (float)analogRead(SENSOR_PIN); if (!bufferFull) { insertValue(reading); Serial.printf("Buffering: [%d/%d] Value: %.1f\n", windowIndex, WINDOW_SIZE, reading); delay(500); return; } // Calculate window metrics float mean = getMean(); float stdDev = getStdDev(mean); // Calculate Z-Score float zScore = 0; if (stdDev > 0.1) { zScore = abs(reading - mean) / stdDev; } Serial.printf("[VAL] Reading: %.1f | Mean: %.1f | StdDev: %.1f | Z: %.2f\n", reading, mean, stdDev, zScore); // Evaluate anomaly threshold if (zScore > 2.5) { Serial.println(" -> [ALERT] ANOMALY DETECTED!"); // Trigger local alerts digitalWrite(ALERT_LED, HIGH); digitalWrite(BUZZER_PIN, HIGH); delay(1000); digitalWrite(ALERT_LED, LOW); digitalWrite(BUZZER_PIN, LOW); } else { // Insert normal value to update rolling average window insertValue(reading); } delay(1000); }
5. Deliverables Summary

Created Artifacts

  • Z-score classifier C++ firmware: edge_aiml.ino.
  • Power profiling sheet tracking CPU usage during calculations.
  • Edge AI model accuracy reports detailing false positive rates.

Verification Proof

  • Screenshot of the Serial Monitor logging Z-score values and alert statements.
  • A video clip showing the warning LED flashing immediately in response to a sudden temperature spike.
6. Closing Explanation: Why We Did This & What It Accomplishes

Architectural Intent & Operational Impact

Why We Did This

What This Accomplishes