#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c_master.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "anomaly_model.h"
static const char *TAG = "anomaly_detect";
/* Hardware config (same as data collection) */
#define I2C_MASTER_SCL_IO 22
#define I2C_MASTER_SDA_IO 21
#define MPU6050_ADDR 0x68
/* Detection parameters */
#define SAMPLE_RATE_HZ 200
#define INPUT_SIZE (WINDOW_SIZE * NUM_AXES)
/* Anomaly threshold (from training script) */
#define ANOMALY_THRESHOLD 0.015000f /* Replace with your value */
/* Normalization constants (from training script) */
static const float NORM_MEAN[INPUT_SIZE] = {
/* Paste the 300 mean values from the training export */
static const float NORM_STD[INPUT_SIZE] = {
/* Paste the 300 std values from the training export */
#define TENSOR_ARENA_SIZE (32 * 1024)
static uint8_t s_tensor_arena[TENSOR_ARENA_SIZE]
__attribute__((aligned(16)));
static i2c_master_bus_handle_t s_i2c_bus;
static i2c_master_dev_handle_t s_mpu_dev;
/* Sensor window buffer */
static float s_window[WINDOW_SIZE][NUM_AXES];
static float s_flat_input[INPUT_SIZE];
/* ---- I2C and MPU6050 init (same as collection firmware) ---- */
static void i2c_init(void)
i2c_master_bus_config_t bus_cfg = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_io_num = I2C_MASTER_SDA_IO,
.flags.enable_internal_pullup = true,
i2c_new_master_bus(&bus_cfg, &s_i2c_bus);
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = MPU6050_ADDR,
i2c_master_bus_add_device(s_i2c_bus, &dev_cfg, &s_mpu_dev);
static void mpu6050_write_reg(uint8_t reg, uint8_t val)
uint8_t buf[2] = {reg, val};
i2c_master_transmit(s_mpu_dev, buf, 2, 100);
static void mpu6050_init(void)
mpu6050_write_reg(0x6B, 0x00);
vTaskDelay(pdMS_TO_TICKS(100));
mpu6050_write_reg(0x1C, 0x08); /* +/- 4g */
mpu6050_write_reg(0x19, 0x04); /* 200 Hz */
mpu6050_write_reg(0x1A, 0x03); /* DLPF 44 Hz */
static void mpu6050_read_accel(float *ax, float *ay, float *az)
i2c_master_transmit_receive(s_mpu_dev, ®, 1, raw, 6, 100);
*ax = (int16_t)((raw[0] << 8) | raw[1]) / 8192.0f;
*ay = (int16_t)((raw[2] << 8) | raw[3]) / 8192.0f;
*az = (int16_t)((raw[4] << 8) | raw[5]) / 8192.0f;
static void led_init(void)
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << LED_PIN),
.mode = GPIO_MODE_OUTPUT,
gpio_set_level(LED_PIN, 0);
/* ---- Capture and normalize ---- */
static void capture_window(void)
int64_t interval_us = 1000000 / SAMPLE_RATE_HZ;
for (int i = 0; i < WINDOW_SIZE; i++) {
int64_t start = esp_timer_get_time();
mpu6050_read_accel(&s_window[i][0], &s_window[i][1],
while ((esp_timer_get_time() - start) < interval_us) {
static void normalize_window(void)
for (int i = 0; i < WINDOW_SIZE; i++) {
for (int j = 0; j < NUM_AXES; j++) {
int idx = i * NUM_AXES + j;
(s_window[i][j] - NORM_MEAN[idx]) / NORM_STD[idx];
/* ---- Anomaly detection task ---- */
static void anomaly_detection_task(void *arg)
/* Initialize TFLite Micro */
const tflite::Model *model = tflite::GetModel(anomaly_model_tflite);
static tflite::MicroMutableOpResolver<3> resolver;
resolver.AddFullyConnected();
static tflite::MicroInterpreter interpreter(
model, resolver, s_tensor_arena, TENSOR_ARENA_SIZE);
interpreter.AllocateTensors();
TfLiteTensor *input = interpreter.input(0);
TfLiteTensor *output = interpreter.output(0);
ESP_LOGI(TAG, "TFLite Micro ready. Arena: %zu bytes",
interpreter.arena_used_bytes());
/* Exponential moving average for smoothing */
bool first_window = true;
/* Capture 0.5-second window */
float in_scale = input->params.scale;
int in_zp = input->params.zero_point;
int8_t *in_data = input->data.int8;
for (int i = 0; i < INPUT_SIZE; i++) {
int32_t q = (int32_t)(s_flat_input[i] / in_scale) + in_zp;
int64_t t_start = esp_timer_get_time();
int64_t t_end = esp_timer_get_time();
/* Dequantize output and compute reconstruction error */
float out_scale = output->params.scale;
int out_zp = output->params.zero_point;
int8_t *out_data = output->data.int8;
for (int i = 0; i < INPUT_SIZE; i++) {
(out_data[i] - out_zp) * out_scale;
(in_data[i] - in_zp) * in_scale;
float diff = original - reconstructed;
/* Exponential moving average */
ema_error = ema_alpha * mse + (1.0f - ema_alpha) * ema_error;
bool is_anomaly = ema_error > ANOMALY_THRESHOLD;
gpio_set_level(LED_PIN, 1);
ESP_LOGW(TAG, "ANOMALY! MSE=%.6f, EMA=%.6f, "
"Threshold=%.6f, Infer=%lld us",
mse, ema_error, ANOMALY_THRESHOLD,
(long long)(t_end - t_start));
gpio_set_level(LED_PIN, 0);
ESP_LOGI(TAG, "Normal. MSE=%.6f, EMA=%.6f, "
(long long)(t_end - t_start));
/* Print statistics every 100 windows */
if ((anomaly_count + normal_count) % 100 == 0) {
ESP_LOGI(TAG, "Stats: %d normal, %d anomalies (%.1f%%)",
normal_count, anomaly_count,
/ (anomaly_count + normal_count));
/* ---- Entry point ---- */
ESP_LOGI(TAG, "Vibration anomaly detector starting");
/* Wait for motor to stabilize */
ESP_LOGI(TAG, "Waiting 3 seconds for motor to stabilize...");
vTaskDelay(pdMS_TO_TICKS(3000));
xTaskCreate(anomaly_detection_task, "anomaly_task",
Comments