Objetivo y caso de uso
Qué construirás: Un sistema de detección de anomalías utilizando I2S en un ESP32 que envía alertas a través de MQTT y proporciona retroalimentación de audio con indicadores LED.
Para qué sirve
- Monitoreo de calidad de audio en tiempo real mediante análisis de señal.
- Detección de ruidos anómalos en sistemas de audio profesional.
- Alertas automáticas en dispositivos IoT cuando se detectan anomalías.
- Control de iluminación LED en respuesta a eventos de audio específicos.
- Integración con sistemas de domótica para mejorar la seguridad del hogar.
Resultado esperado
- Latencias de respuesta de menos de 100 ms en la detección de anomalías.
- Transmisión de datos a través de MQTT con un throughput de al menos 10 paquetes/s.
- Indicadores LED que reflejan el estado del sistema en tiempo real.
- Mensajes de alerta enviados al broker MQTT en menos de 200 ms tras la detección.
- Capacidad de procesar señales de audio de hasta 48 kHz sin pérdida de calidad.
Público objetivo: Desarrolladores de sistemas embebidos; Nivel: Avanzado
Arquitectura/flujo: Captura de audio con I2S en ESP32 → Análisis de señal → Detección de anomalías → Envío de alertas MQTT → Activación de indicadores LED.
Nivel: Avanzado
Prerrequisitos
Sistemas operativos probados
- Windows 11 Pro 23H2 (build 22631)
- Ubuntu 22.04.4 LTS (kernel 5.15)
- macOS 14.5 (Sonoma)
Toolchain exacta (versiones cerradas y reproducibles)
- Python: 3.11.9
- PlatformIO Core (CLI): 6.1.14
- PlatformIO Platform: espressif32@6.5.0
- Framework Arduino para ESP32 (a través de PlatformIO): 2.0.14
- Compilador xtensa-esp32-elf-gcc: 8.4.0 (incluido por espressif32@6.5.0)
- esptool.py: 4.7
- Controlador USB-UART para Espressif ESP32-DevKitC V4: CP210x (Silicon Labs) v10.1.10 (Windows). En macOS y Linux generalmente no se requiere instalación manual.
- Broker MQTT de referencia para validación: Eclipse Mosquitto 2.0.18 (opcional, para pruebas en PC)
- Bibliotecas del proyecto (se fijan en platformio.ini):
- PubSubClient 2.8 (MQTT)
- Adafruit NeoPixel 1.12.0
- arduinoFFT 1.5.6
Notas:
– Se fuerza el entorno a versiones concretas para favorecer la reproducibilidad.
– El framework Arduino 2.0.14 usa ESP-IDF 4.4.x bajo el capó, con API driver/i2s estable.
Materiales
- Placa de desarrollo: Espressif ESP32-DevKitC V4 (ESP32-WROOM-32)
- Micrófono digital I2S: INMP441 (módulo con pines WS/LRCL, SCK/BCLK, SD/DOUT, L/R)
- Amplificador/DAC I2S: MAX98357A (módulo con BCLK, LRC, DIN, SD/GAIN)
- Tira LED o LED suelto WS2812B (al menos 1 LED)
- Fuente de alimentación:
- USB 5 V para la DevKitC V4
- 5 V para el WS2812B (puede tomarse del mismo USB si la corriente total lo permite)
- Protoboard y cables dupont
- Un altavoz pequeño (4–8 Ω) para el MAX98357A
- Opcional pero recomendado: conversor de nivel lógico 3.3 V→5 V para la data del WS2812B (muchos funcionan sin él a 3.3 V, pero no es lo ideal)
Objetivo del proyecto: i2s-anomaly-detection-mqtt
– Captura de audio con INMP441 vía I2S0
– Generación de tonos con MAX98357A vía I2S1 para feedback acústico
– Detección de anomalías acústicas (basada en características espectrales) en tiempo real
– Telemetría MQTT con puntajes y eventos de anomalía
– Señalización visual con WS2812B (estado/calibración/anomalía)
Preparación y conexión
Consideraciones de pines y buses
- El ESP32 dispone de dos controladores I2S (I2S_NUM_0 e I2S_NUM_1). Usaremos:
- I2S0 para el micrófono (RX)
- I2S1 para el amplificador MAX98357A (TX)
- Evitaremos pines de “strapping” en señales sensibles de arranque (GPIO0, GPIO2, GPIO15) para minimizar riesgos de boot fallido.
Tabla de conexiones
| Componente | Señal/Pin módulo | Pin ESP32-DevKitC V4 | Notas |
|---|---|---|---|
| INMP441 | VDD | 3V3 | Alimentación 3.3 V |
| INMP441 | GND | GND | Tierra común |
| INMP441 | L/R | GND | Salida en canal IZQUIERDO |
| INMP441 | SCK (BCLK) | GPIO14 | I2S0 BCLK |
| INMP441 | WS (LRCL) | GPIO13 | I2S0 LRCLK |
| INMP441 | SD (DOUT) | GPIO32 | I2S0 DATA IN |
| MAX98357A | VIN | 5V | Alimentación 5 V |
| MAX98357A | GND | GND | Tierra común |
| MAX98357A | BCLK | GPIO26 | I2S1 BCLK |
| MAX98357A | LRC | GPIO25 | I2S1 LRCLK |
| MAX98357A | DIN | GPIO22 | I2S1 DATA OUT |
| MAX98357A | SD (EN/Mute) | GPIO27 | Control de mute/enable (alto = activo) |
| WS2812B | +5V | 5V | Alimentación LED |
| WS2812B | GND | GND | Tierra común |
| WS2812B | DIN | GPIO4 | Datos LED (RMT/bitbang) |
Recomendaciones:
– Conecta todas las tierras en común (ESP32, micrófono, MAX98357A, WS2812B).
– Si tu WS2812B es una tira larga, inyecta 5 V por ambos extremos y añade un condensador electrolítico 1000 µF/6.3 V entre +5 V y GND.
– Coloca una resistencia serie de 220–470 Ω en la línea DIN del WS2812B para mejorar integridad de señal.
– Si el cable entre ESP32 y LED es largo, usa conversor de nivel lógico 3.3→5 V.
Código completo (Arduino + PlatformIO)
A continuación se provee un proyecto mínimo viable completo con:
– Inicialización I2S RX (INMP441) e I2S TX (MAX98357A)
– Extracción de características (RMS y centroide espectral con FFT)
– Calibración de referencia (baseline) durante ~10 s
– Cálculo de puntaje de anomalía (z-score combinado)
– Publicación por MQTT en JSON
– Señalización con WS2812B y beep acústico en anomalías
Estructura propuesta del proyecto (PlatformIO):
– platformio.ini
– src/main.cpp
platformio.ini
; PlatformIO configuration for Espressif ESP32-DevKitC V4 (ESP32-WROOM-32)
[env:esp32dev]
platform = espressif32@6.5.0
board = esp32dev
framework = arduino
platform_packages =
framework-arduinoespressif32@~2.0.14
monitor_speed = 115200
upload_speed = 921600
board_build.flash_mode = dio
board_build.f_cpu = 240000000L
lib_deps =
knolleary/PubSubClient@2.8
adafruit/Adafruit NeoPixel@1.12.0
kosme/arduinoFFT@1.5.6
build_flags =
-DWIFI_SSID=\"TU_SSID\"
-DWIFI_PASS=\"TU_PASSWORD\"
-DMQTT_HOST=\"192.168.1.50\"
-DMQTT_PORT=1883
-DMQTT_CLIENT_ID=\"esp32-devkitc-v4-i2s-anomaly\"
-DMQTT_TOPIC_PUB=\"esp32/devkitc/anomaly\"
-DMQTT_TOPIC_STATUS=\"esp32/devkitc/status\"
-DAUDIO_SAMPLE_RATE=16000
-DAUDIO_FRAME_SAMPLES=1024
-DWS2812_PIN=4
-DWS2812_NUM=1
-DMAX98357_ENABLE_PIN=27
; Notas de toolchain:
; PlatformIO Core 6.1.14, esptool.py 4.7, xtensa-esp32-elf-gcc 8.4.0 (vía espressif32@6.5.0)
Asegúrate de reemplazar TU_SSID, TU_PASSWORD y MQTT_HOST por tus valores reales.
src/main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_NeoPixel.h>
#include "arduinoFFT.h"
#include "driver/i2s.h"
// ======================= Configuración por macros (desde platformio.ini) =======================
#ifndef WIFI_SSID
#define WIFI_SSID "RELLENAR"
#endif
#ifndef WIFI_PASS
#define WIFI_PASS "RELLENAR"
#endif
#ifndef MQTT_HOST
#define MQTT_HOST "192.168.1.50"
#endif
#ifndef MQTT_PORT
#define MQTT_PORT 1883
#endif
#ifndef MQTT_CLIENT_ID
#define MQTT_CLIENT_ID "esp32-devkitc-v4-i2s-anomaly"
#endif
#ifndef MQTT_TOPIC_PUB
#define MQTT_TOPIC_PUB "esp32/devkitc/anomaly"
#endif
#ifndef MQTT_TOPIC_STATUS
#define MQTT_TOPIC_STATUS "esp32/devkitc/status"
#endif
#ifndef AUDIO_SAMPLE_RATE
#define AUDIO_SAMPLE_RATE 16000
#endif
#ifndef AUDIO_FRAME_SAMPLES
#define AUDIO_FRAME_SAMPLES 1024
#endif
#ifndef WS2812_PIN
#define WS2812_PIN 4
#endif
#ifndef WS2812_NUM
#define WS2812_NUM 1
#endif
#ifndef MAX98357_ENABLE_PIN
#define MAX98357_ENABLE_PIN 27
#endif
// ======================= Pines I2S (coherentes con la tabla) =======================
static const i2s_port_t I2S_MIC = I2S_NUM_0;
static const int I2S_MIC_BCLK = 14; // INMP441 SCK/BCLK
static const int I2S_MIC_LRCL = 13; // INMP441 WS/LRCL
static const int I2S_MIC_DOUT = 32; // INMP441 SD/DOUT
static const i2s_port_t I2S_SPK = I2S_NUM_1;
static const int I2S_SPK_BCLK = 26; // MAX98357A BCLK
static const int I2S_SPK_LRCL = 25; // MAX98357A LRC
static const int I2S_SPK_DIN = 22; // MAX98357A DIN
// ======================= Objetos globales =======================
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
Adafruit_NeoPixel pixel(WS2812_NUM, WS2812_PIN, NEO_GRB + NEO_KHZ800);
arduinoFFT FFT = arduinoFFT(); // usaremos interfaz por vectores de doble precisión
// Buffers y variables de audio
static const uint16_t N = AUDIO_FRAME_SAMPLES; // tamaño de ventana FFT (potencia de 2)
int32_t i2s_rx_buffer[N]; // Lectura cruda 32-bit desde I2S
double vReal[N];
double vImag[N];
// Características y baseline
volatile bool baseline_ready = false;
const uint32_t baseline_time_ms = 10000; // 10 s de calibración
uint32_t baseline_start_ms = 0;
uint32_t frames_seen = 0;
double mean_rms = 0.0, var_rms = 0.0;
double mean_cent = 0.0, var_cent = 0.0;
const double anomaly_threshold = 6.0; // suma de |z-scores| de RMS y centroide
// Utilidades LED
void led_color(uint8_t r, uint8_t g, uint8_t b) {
pixel.setPixelColor(0, pixel.Color(r, g, b));
pixel.show();
}
// ======================= Inicialización I2S MIC (RX) =======================
bool i2s_mic_init() {
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = AUDIO_SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // INMP441 cableado a canal izquierdo
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 256,
.use_apll = true,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
i2s_pin_config_t pin_cfg = {
.bck_io_num = I2S_MIC_BCLK,
.ws_io_num = I2S_MIC_LRCL,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = I2S_MIC_DOUT
};
esp_err_t err;
err = i2s_driver_install(I2S_MIC, &cfg, 0, NULL);
if (err != ESP_OK) return false;
err = i2s_set_pin(I2S_MIC, &pin_cfg);
if (err != ESP_OK) return false;
err = i2s_set_clk(I2S_MIC, AUDIO_SAMPLE_RATE, I2S_BITS_PER_SAMPLE_32BIT, I2S_CHANNEL_MONO);
return err == ESP_OK;
}
// ======================= Inicialización I2S SPK (TX) =======================
bool i2s_spk_init() {
pinMode(MAX98357_ENABLE_PIN, OUTPUT);
digitalWrite(MAX98357_ENABLE_PIN, LOW); // arranca en mute
i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
.sample_rate = AUDIO_SAMPLE_RATE, // mismo sample rate para simplicidad
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // el MAX98357A trabaja bien a 16 bits
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // estéreo (duplicaremos)
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = 256,
.use_apll = false,
.tx_desc_auto_clear = true,
.fixed_mclk = 0
};
i2s_pin_config_t pin_cfg = {
.bck_io_num = I2S_SPK_BCLK,
.ws_io_num = I2S_SPK_LRCL,
.data_out_num = I2S_SPK_DIN,
.data_in_num = I2S_PIN_NO_CHANGE
};
if (i2s_driver_install(I2S_SPK, &cfg, 0, NULL) != ESP_OK) return false;
if (i2s_set_pin(I2S_SPK, &pin_cfg) != ESP_OK) return false;
if (i2s_set_clk(I2S_SPK, AUDIO_SAMPLE_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO) != ESP_OK) return false;
digitalWrite(MAX98357_ENABLE_PIN, HIGH); // habilita amplificador
return true;
}
// ======================= Generación de beep (feedback) =======================
void beep(uint16_t freq_hz = 880, uint16_t ms = 120, uint16_t amplitude = 4000) {
const uint32_t samples = (AUDIO_SAMPLE_RATE * ms) / 1000;
// Estéreo intercalado (L,R) a 16-bit
for (uint32_t n = 0; n < samples; ++n) {
float t = (float)n / AUDIO_SAMPLE_RATE;
int16_t s = (int16_t)(amplitude * sinf(2.0f * PI * freq_hz * t));
uint32_t twoch = ((uint16_t)s << 16) | ((uint16_t)s); // L=R
size_t written = 0;
i2s_write(I2S_SPK, &twoch, sizeof(twoch), &written, portMAX_DELAY);
}
}
// ======================= Conectividad WiFi + MQTT =======================
void mqtt_reconnect() {
while (!mqtt.connected()) {
Serial.print(F("Conectando a MQTT... "));
if (mqtt.connect(MQTT_CLIENT_ID)) {
Serial.println(F("OK"));
mqtt.publish(MQTT_TOPIC_STATUS, "online", true);
} else {
Serial.printf("fallo rc=%d, reintento en 3 s\n", mqtt.state());
delay(3000);
}
}
}
void wifi_connect() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.printf("Conectando a WiFi SSID=%s\n", WIFI_SSID);
uint32_t t0 = millis();
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
if (millis() - t0 > 20000) {
Serial.println("\nTimeout WiFi, reintentando...");
WiFi.disconnect(true);
WiFi.begin(WIFI_SSID, WIFI_PASS);
t0 = millis();
}
}
Serial.printf("\nWiFi conectado: %s (RSSI=%d dBm), IP=%s\n",
WiFi.SSID().c_str(), WiFi.RSSI(), WiFi.localIP().toString().c_str());
mqtt.setServer(MQTT_HOST, MQTT_PORT);
}
// ======================= Procesamiento de características =======================
static inline double compute_rms(const double* x, uint16_t len) {
double acc = 0.0;
for (uint16_t i = 0; i < len; ++i) acc += x[i] * x[i];
return sqrt(acc / len);
}
static inline double compute_spectral_centroid(const double* mag, uint16_t bins, double fs) {
// bins corresponde a N/2 después de FFT
double num = 0.0, den = 0.0;
for (uint16_t k = 1; k < bins; ++k) { // evita DC
double f = (double)k * fs / (2.0 * bins);
num += f * mag[k];
den += mag[k];
}
return (den > 1e-9) ? (num / den) : 0.0;
}
// Welford para media/varianza en línea
struct OnlineStats {
double mean = 0.0;
double M2 = 0.0;
uint32_t n = 0;
void add(double x) {
n++;
double delta = x - mean;
mean += delta / n;
double delta2 = x - mean;
M2 += delta * delta2;
}
double variance() const { return (n > 1) ? (M2 / (n - 1)) : 0.0; }
};
OnlineStats stats_rms, stats_cent;
// ======================= Setup =======================
void setup() {
Serial.begin(115200);
delay(200);
pixel.begin();
pixel.setBrightness(30); // brillo moderado
led_color(255, 165, 0); // naranja: arranque/calibración
if (!i2s_mic_init()) {
Serial.println("Error inicializando I2S MIC");
led_color(255, 0, 255); // magenta error
while (true) delay(1000);
}
if (!i2s_spk_init()) {
Serial.println("Error inicializando I2S SPK");
led_color(255, 0, 255);
while (true) delay(1000);
}
wifi_connect();
mqtt.setBufferSize(1024); // para payload JSON más amplio
mqtt_reconnect();
baseline_start_ms = millis();
Serial.println("Calibración de baseline en curso (~10 s)...");
}
// ======================= Loop principal =======================
void loop() {
if (WiFi.status() == WL_CONNECTED) {
if (!mqtt.connected()) mqtt_reconnect();
mqtt.loop();
}
// Leer un frame de AUDIO_FRAME_SAMPLES desde I2S (32-bit)
size_t bytes_read = 0;
i2s_read(I2S_MIC, (void*)i2s_rx_buffer, sizeof(i2s_rx_buffer), &bytes_read, portMAX_DELAY);
uint16_t samples_read = bytes_read / sizeof(int32_t);
if (samples_read != N) {
// Ajuste básico; en la práctica debería llegar N por configuración de DMA
return;
}
// Convertir a double, quitar offset y escalar
// INMP441 a 32-bit, útil tomar los 24 bits superiores (datos suelen venir en MSB)
static double dc_acc = 0.0;
for (uint16_t i = 0; i < N; ++i) {
int32_t s = i2s_rx_buffer[i] >> 8; // 24-bit significativo
double x = (double)s / 8388608.0; // normaliza aprox a [-1,1]
dc_acc += 0.0001 * (x - dc_acc); // filtro DC lento
x -= dc_acc;
// Ventana Hann para FFT
double w = 0.5 * (1.0 - cos((2.0 * PI * i) / (N - 1)));
vReal[i] = x * w;
vImag[i] = 0.0;
}
// Característica temporal: RMS (sin ventana)
double rms = compute_rms(vReal, N);
// FFT -> magnitud espectral
FFT.Windowing(vReal, N, FFT_WIN_TYP_RECTANGLE, FFT_FORWARD); // ya aplicamos Hann manual
FFT.Compute(vReal, vImag, N, FFT_FORWARD);
FFT.ComplexToMagnitude(vReal, vImag, N);
// vReal ahora contiene magnitudes. Usar N/2 bins útiles.
uint16_t bins = N / 2;
double centroid = compute_spectral_centroid(vReal, bins, (double)AUDIO_SAMPLE_RATE);
// Acumular baseline durante ~10 s
if (!baseline_ready) {
stats_rms.add(rms);
stats_cent.add(centroid);
frames_seen++;
if (millis() - baseline_start_ms >= baseline_time_ms) {
mean_rms = stats_rms.mean;
var_rms = stats_rms.variance();
mean_cent = stats_cent.mean;
var_cent = stats_cent.variance();
baseline_ready = true;
Serial.printf("Baseline lista: mean_rms=%.6f, std_rms=%.6f, mean_cent=%.1f Hz, std_cent=%.1f Hz\n",
mean_rms, sqrt(var_rms), mean_cent, sqrt(var_cent));
led_color(0, 255, 0); // verde: listo
mqtt.publish(MQTT_TOPIC_STATUS, "baseline_ready", true);
} else {
// Indicar calibración con pulso suave
static uint32_t lastBlink = 0;
if (millis() - lastBlink > 250) {
lastBlink = millis();
led_color(255, 165, 0); // naranja
}
}
return;
}
// Calcular z-scores y puntaje de anomalía
double std_rms = sqrt(var_rms);
double std_cent = sqrt(var_cent);
double z_rms = (std_rms > 1e-9) ? (rms - mean_rms) / std_rms : 0.0;
double z_cent = (std_cent > 1e-9) ? (centroid - mean_cent) / std_cent : 0.0;
double anomaly_score = fabs(z_rms) + fabs(z_cent);
bool is_anomaly = anomaly_score >= anomaly_threshold;
// Feedback visual/acústico
if (is_anomaly) {
led_color(255, 0, 0);
beep(880, 90, 3000);
} else {
led_color(0, 64, 0); // verde tenue en operación normal
}
// Publicar MQTT (JSON compacto)
static char payload[768];
snprintf(payload, sizeof(payload),
"{\"device\":\"%s\",\"ts\":%lu,"
"\"rms\":%.6f,\"centroid_hz\":%.1f,"
"\"z_rms\":%.2f,\"z_cent\":%.2f,\"score\":%.2f,"
"\"anomaly\":%s}",
MQTT_CLIENT_ID, (unsigned long)millis(),
rms, centroid,
z_rms, z_cent, anomaly_score,
is_anomaly ? "true" : "false"
);
mqtt.publish(MQTT_TOPIC_PUB, payload);
}
Breve explicación de partes clave:
– i2s_mic_init / i2s_spk_init: configura controladores I2S separados (RX para INMP441, TX para MAX98357A) con los pines exactos del apartado de conexiones.
– Lectura I2S: se leen 1024 muestras 32-bit, se normalizan y se aplica ventana Hann.
– Extracción de características:
– RMS: energía del frame.
– Centroide espectral: “centro de masa” del espectro, sensible a contenido de alta frecuencia.
– Baseline/calibración: durante 10 s calcula media y varianza (Welford) de RMS y centroide.
– Detección: suma de |z-scores|; si supera 6.0, se marca anomalía.
– Feedback:
– WS2812B: naranja (calibración), verde (normal), rojo (anomalía).
– MAX98357A: beep de ~880 Hz en anomalías.
– MQTT: publica JSON en esp32/devkitc/anomaly con métricas y bandera de anomalía.
Compilación/flash/ejecución
1) Instalar PlatformIO Core (CLI) en Python 3.11.9
- Windows/macOS/Linux:
- Asegúrate de tener Python 3.11 en PATH.
- Instala versión fija:
python -m pip install --upgrade pip
python -m pip install platformio==6.1.14
pio --version
Debe imprimir “PlatformIO Core, version 6.1.14”.
2) Inicializar proyecto
- En una carpeta vacía:
pio project init --board esp32dev - Sustituye el contenido de platformio.ini por el mostrado arriba.
- Crea src/main.cpp con el código anterior.
3) Drivers USB-UART
- Windows: instala “CP210x Universal Windows Driver v10.1.10” desde Silicon Labs.
- macOS/Linux: normalmente no se requiere. Verifica el puerto:
- macOS: /dev/cu.SLAB_USBtoUART
- Linux: /dev/ttyUSB0 o /dev/ttyUSB1
- Windows: COMx
4) Comandos de build, upload y monitor
- Compilar:
pio run - Flashear (conecta la placa por USB):
pio run -t upload - Monitor serie (115200 baudios):
pio device monitor -b 115200
5) Comprobación del broker MQTT (opcional local)
- Instala Mosquitto 2.0.18 (si no tienes broker).
- Suscríbete para ver telemetría:
mosquitto_sub -h 127.0.0.1 -p 1883 -t esp32/devkitc/anomaly -v
mosquitto_sub -h 127.0.0.1 -p 1883 -t esp32/devkitc/status -v
Asegúrate de que MQTT_HOST en platformio.ini apunte a tu broker.
Validación paso a paso
1) Arranque y calibración
– Abre el monitor serie:
– Debes ver: “Conectando a WiFi…”, RSSI e IP asignada.
– Mensaje “Calibración de baseline en curso (~10 s)…”.
– LED WS2812B:
– Naranja durante la calibración (~10 s).
– MQTT:
– Topic esp32/devkitc/status: “online” y luego “baseline_ready”.
2) Post-calibración (estado normal)
– LED: verde tenue estable (normal).
– En el monitor serie se mostrará el resumen de baseline (mean/std de RMS y centroid).
– En el broker, llegarán mensajes JSON en esp32/devkitc/anomaly cada frame o cada pocos cientos de ms:
– Claves: rms, centroid_hz, z_rms, z_cent, score, anomaly.
3) Forzar una anomalía acústica
– Golpea suavemente la mesa, aplaude cerca o genera un ruido agudo.
– LED: pasa a rojo durante la detección del evento.
– Altavoz: debe sonar un beep breve (~880 Hz).
– MQTT: el JSON debe contener «anomaly»:true y un “score” significativamente > 6.0.
4) Verificación de parámetros
– Observa en JSON cómo varía “centroid_hz” (ruidos agudos subirán el centroide).
– “rms” aumentará con niveles de sonido más altos.
– “z_rms” y “z_cent” deberían acercarse a 0 en condiciones normales; crecerán en anomalías.
5) Suscripción desde PC con mosquitto_sub
– Ejemplo de salida:
esp32/devkitc/anomaly {"device":"esp32-devkitc-v4-i2s-anomaly","ts":10456,"rms":0.012345,"centroid_hz":1450.2,"z_rms":0.22,"z_cent":0.18,"score":0.40,"anomaly":false}
esp32/devkitc/anomaly {"device":"esp32-devkitc-v4-i2s-anomaly","ts":12890,"rms":0.098765,"centroid_hz":3800.5,"z_rms":4.10,"z_cent":3.20,"score":7.30,"anomaly":true}
6) Audio de salida
– Al detectar anomalía, el MAX98357A reproduce un beep corto; si no se escucha, revisa altavoz, cableado y nivel de mute (GPIO27 en alto).
Troubleshooting (errores típicos y soluciones)
1) El ESP32 no aparece como puerto serie o upload falla
– Verifica el cable USB (que sea de datos, no solo carga).
– Instala/actualiza el driver CP210x (Windows).
– Prueba a bajar la velocidad de upload: en platformio.ini usa upload_speed = 460800.
– Pulsa BOOT mientras inicia el upload, o reset si fuera necesario.
2) Boot loop o la placa no arranca tras cableado
– Evita usar pines de strapping (GPIO0, GPIO2, GPIO15) para señales externas. La guía ya los evita.
– Desconecta temporalmente periféricos y prueba solo la placa; si arranca, revisa resistencias/pulls en tu montaje.
3) El micrófono INMP441 no devuelve datos válidos (silencio o valores erráticos)
– Comprueba que L/R esté a GND para canal izquierdo.
– Revisa pines: SD→GPIO32, WS→GPIO13, SCK→GPIO14, VDD→3.3 V.
– Asegura tierra común.
– Mantén sample_rate=16000 y 32 bits en RX; INMP441 suele requerir 24 bits; usamos 32 con desplazamiento.
4) El MAX98357A no emite audio
– Verifica que SD/EN (GPIO27) esté en ALTO tras inicialización.
– Revisa BCLK (GPIO26), LRC (GPIO25), DIN (GPIO22).
– Comprueba el altavoz y su conexión, y que la fuente 5 V esté presente.
– Asegura la coincidencia de sample rate entre TX y la señal generada (usamos AUDIO_SAMPLE_RATE).
5) WS2812B no enciende o parpadea errático
– Añade resistencia serie de 330 Ω en DIN y condensador de 1000 µF en la alimentación de la tira.
– Usa un buen GND común y alimenta a 5 V estable.
– Si el cable DIN es largo, usa conversor de nivel 3.3→5 V.
– Disminuye el brillo (pixel.setBrightness).
6) No conecta a MQTT o se desconecta frecuentemente
– Verifica MQTT_HOST y puerto 1883 (sin TLS en este ejemplo).
– Asegúrate de que el broker permite clientes anónimos o configura usuario/clave en PubSubClient (no cubierto aquí).
– Aumenta keepalive o reduce frecuencia de publicaciones si el broker aplica rate limiting.
7) Anomalías constantes o nunca detecta
– Recalibra baseline en un entorno silencioso (~10 s tras reset).
– Ajusta anomaly_threshold (p. ej., 5.0 para mayor sensibilidad o 8.0 para menos).
– Valida que centroid_hz y rms varíen con estímulos; si no, revisa micrófono.
8) Desbordes/tiempos en MQTT (payload truncado)
– Aumenta mqtt.setBufferSize(1024) como en el código.
– Verifica que la longitud del JSON no exceda el buffer.
Mejoras/variantes
- Modelo TinyML:
- Integrar TensorFlow Lite for Microcontrollers para un autoencoder o detector de anomalías espectral. Preprocesa con mel-espectrogramas y despliega un modelo 1D.
- Persistencia de baseline:
- Guardar mean/std en NVS/Preferences tras calibración y restaurarlos en el arranque para evitar recalibrar cada vez.
- MQTT seguro:
- Migrar a TLS (puerto 8883) con certificados CA. Usar WiFiClientSecure y PubSubClient con setServer(MQTT_HOST, 8883).
- Downsampling/banda de interés:
- Filtrar banda (p. ej., 300–3400 Hz) para voz/máquinas y refinar el centroide o usar centroides por bandas.
- Indicador multicolor más rico:
- WS2812B en patrones animados (pulso, barrido) según severidad del score.
- Buffer circular y detección por ráfagas:
- Aplicar ventana deslizante, agregación de scores y supresión de múltiples eventos por debouncing temporal.
- Telemetría ampliada:
- Publicar espectro reducido (p. ej., 32 bins logarítmicos) o features adicionales: ZCR, entropía espectral, roll-off.
Checklist de verificación
- [ ] He instalado PlatformIO Core 6.1.14 en Python 3.11.9 y confirmé pio –version.
- [ ] He creado el proyecto con board=esp32dev y sustituido platformio.ini por el proporcionado.
- [ ] He fijado WIFI_SSID, WIFI_PASS y MQTT_HOST en build_flags de platformio.ini.
- [ ] He cableado el INMP441 a GPIO14/13/32 con L/R a GND y alimentación 3.3 V.
- [ ] He cableado el MAX98357A a GPIO26/25/22 y EN en GPIO27, con alimentación 5 V y altavoz conectado.
- [ ] He cableado el WS2812B a GPIO4 con 5 V y GND comunes, preferiblemente con resistencia en DIN.
- [ ] La compilación pio run finaliza sin errores.
- [ ] La carga pio run -t upload se realiza y puedo abrir pio device monitor -b 115200.
- [ ] Veo “baseline_ready” tras ~10 s y LED verde.
- [ ] Al provocar un ruido anómalo, LED rojo + beep, y el topic esp32/devkitc/anomaly publica anomaly=true.
- [ ] En estado normal, el score permanece estable y bajo el umbral.
Con este caso práctico, has implementado un pipeline completo de i2s-anomaly-detection-mqtt en el hardware exacto “Espressif ESP32-DevKitC V4 (ESP32-WROOM-32) + INMP441 + MAX98357A + WS2812B”, con toolchain y versiones fijadas, incluyendo captura I2S, procesamiento básico de audio, notificación MQTT y feedback multimodal.
Encuentra este producto y/o libros sobre este tema en Amazon
Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.



