This tutorial covers how to implement an ESP32 anomaly detection system using Edge Impulse and machine learning. In more detail, we will detect when there is an anomaly in the CO2 concentration and volatile organic compounds. Therefore, we will implement a machine learning model that is capable of identifying if there are some values outside the normal range. To measure the concentration of CO2 and volatile organic compounds we will use ESP32 and CCS811.
Table of contents
What is anomaly detection?
To define anomaly detection we can use this:
In data analysis, anomaly detection (also outlier detection) is the identification of rare items, events or observations which raise suspicions by differing significantly from the majority of the data
https://en.wikipedia.org/wiki/Anomaly_detection
In other words, anomaly detection is the activiy that identifies those values and observations that do not adhere to a pattern that is considered a normal pattern.
To achieve this goal, we will use machine learning and ESP32 in order to identify those values, retrieved from the sensor, that do not belong to the normal pattern. There are several ways to implement anomaly detection with ESP32, in this tutorial, we will use the edge impulse that makes it easy to build the model.
How to implement anomaly detection system with ESP32
To implement an ESP32 anomaly detection system it is necessary to divide it in two different steps:
- acquire CO2 and tVoC values to define the normal pattern
- build the machine learning model and use it with ESP32 to detect anomalies
Acquire data using ESP32, CCS811 and Edge Impulse
The first step is acquiring data using ESP32 and CCS811: we will acquire CO2 concentration and tVoC (volatile organic compounds). This data will be forwarded to Edge Impulse. Before building this project is necessary to create an Edge Impulse account for free and a new project.
Upload this code to the ESP32:
#include <Arduino.h>
#include <Wire.h> // I2C library
#include "ccs811.h" // CCS811 library
#define FREQUENCY_HZ 1
#define INTERVAL_MS (1000 / (FREQUENCY_HZ + 1))
// Initialize CCS811
CCS811 ccs811(-1);
static unsigned long last_interval_ms = 0;
void setup() {
Serial.begin(115200);
Serial.println("Air Quality monitoring with ML");
// Enable I2C
Wire.begin();
// Enable CCS811
bool ok= ccs811.begin();
if( !ok ) Serial.println("setup: CCS811 begin FAILED");
// Start measuring
ok= ccs811.start(CCS811_MODE_1SEC);
if( !ok ) Serial.println("setup: CCS811 start FAILED");
}
void loop() {
uint16_t eco2, etvoc, errstat, raw;
if (millis() > last_interval_ms + INTERVAL_MS) {
last_interval_ms = millis();
ccs811.read(&eco2,&etvoc,&errstat,&raw);
Serial.print(eco2);
Serial.print(",");
Serial.println(etvoc);
}
}
Code language: C++ (cpp)
This code acquires data every 1 second and write CO2 concentration and tVoC to the serial output. Next, run the edge impulse forward:

Now go to Data acquisition and you should see it:

Now start sampling data using sample length long enough. At the end, you have the raw data samples that we will use to detect anomaly using ESP32 and machine learning:

This is the normal pattern therefore each value measured outside of this pattern will be recognized as anomaly.
Other useful content:
Building the machine learning model to detect the anomaly with ESP32
Next, let us create the machine learning model that will use on the ESP32 to detect anomalies:

Then, let us extract features from the raw data and under anomaly detection select the following axes:

Finally, you can train the model and download it using the Deploymnent item.
Anomaly detection using ESP32
In this last step, we will use the machine learning model created before to detect anomaly with ESP32 and integrate this model with CCS811 to detect anomalies in the CO2 and tVoC:
#include <Arduino.h>
#include <air_quality_inference.h>
#include <Wire.h> // I2C library
#include "ccs811.h" // CCS811 library
#define FREQUENCY_HZ 1
#define INTERVAL_MS (1000 / (FREQUENCY_HZ + 1))
float features[EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE];
size_t feature_ix = 0;
// Initialize CCS811
CCS811 ccs811(-1);
void setup() {
Serial.begin(115200);
Serial.println("Air Quality monitoring with ML");
// Enable I2C
Wire.begin();
// Enable CCS811
bool ok= ccs811.begin();
if( !ok ) Serial.println("setup: CCS811 begin FAILED");
// Start measuring
ok= ccs811.start(CCS811_MODE_1SEC);
if( !ok ) Serial.println("setup: CCS811 start FAILED");
Serial.print("Features: ");
Serial.println(EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE);
Serial.print("Label count: ");
Serial.println(EI_CLASSIFIER_LABEL_COUNT);
}
void loop() {
uint16_t eco2, etvoc, errstat, raw;
static unsigned long last_interval_ms = 0;
if (millis() > last_interval_ms + INTERVAL_MS) {
last_interval_ms = millis();
ccs811.read(&eco2,&etvoc,&errstat,&raw);
Serial.print(eco2);
Serial.print(",");
Serial.println(etvoc);
// fill the features buffer
features[feature_ix++] = eco2;
features[feature_ix++] = etvoc;
// Check if we are ready to reun the inference
if (feature_ix == EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) {
Serial.println("Running the inference...");
signal_t signal;
ei_impulse_result_t result;
int err = numpy::signal_from_buffer(features, EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, &signal);
if (err != 0) {
ei_printf("Failed to create signal from buffer (%d)\n", err);
return;
}
EI_IMPULSE_ERROR res = run_classifier(&signal, &result, true);
if (res != 0) return;
ei_printf("Predictions ");
ei_printf("(DSP: %d ms., Classification: %d ms., Anomaly: %d ms.)",
result.timing.dsp, result.timing.classification, result.timing.anomaly);
ei_printf(": \n");
for (size_t ix = 0; ix < EI_CLASSIFIER_LABEL_COUNT; ix++) {
ei_printf(" %s: %.5f\n", result.classification[ix].label, result.classification[ix].value);
}
#if EI_CLASSIFIER_HAS_ANOMALY == 1
ei_printf(" anomaly score: %.3f\n", result.anomaly);
Serial.println("Anomaly Score");
Serial.println(result.anomaly);
delay(1000);
#endif
feature_ix = 0;
Serial.println("==========");
}
}
}
/**
* @brief Printf function uses vsnprintf and output using Arduino Serial
*
* @param[in] format Variable argument list
*/
void ei_printf(const char *format, ...) {
static char print_buf[1024] = { 0 };
va_list args;
va_start(args, format);
int r = vsnprintf(print_buf, sizeof(print_buf), format, args);
va_end(args);
if (r > 0) {
Serial.write(print_buf);
}
}
Code language: C++ (cpp)
Run the code on the ESP32.
Remember that the CCS811 sensor needs some time before reading correct values
How to detect anomaly with ESP32
Now it is time to detect anomaly with ESP32. Try to run the code above and when the values read adhere to the normal pattern you will have:

Now try to blow on the sensor and you will notice that the machine learning model will detect the anomaly.
Wrapping up
At the end of this post, we have implemented an ESP32 anomaly detection system that capable of identify anomalies in the CO2 e tVoC concentration. First we have detected the normal pattern acquiring data in a controlled environment and later using this pattern to detect the anomalies.
I’d like to adopt this idea for a smart watermeter: leakage detection. Unusual behaviour (like slow, but steady flow at night) should trigger. Could this be done?
Yes you should try. It is very interesting. You should acquire what is the “normal” flow and from this you can define what’s the anomaly.
If you can get it working let me know
I would like to detect the fungal attack on plant.will it be possible to do with this?