Free Shipping over ₹1299

Lesson 6 : AI On ESP32

Code

/****************************************************
 *  Project   : ESP32 AI Voice Assistant
 *  Board     : ESP32-S3 / ESP32-C3 / ESP32 CLASSIC
 *  Author    : OceanLabz
 *  Version   : 1.0.0
 *  Date      : 2025-02-10
 *
 *  Description:
 *  --------------------------------------------------------
 *  - Records voice using INMP441 I2S microphone
 *  - Converts speech to text using OpenAI Whisper
 *  - Sends text to ChatGPT for AI response
 *  - Converts AI response to speech using ElevenLabs / OpenAI
 *  - Plays MP3 audio via external I2S DAC
 *
 *
 *  YouTube   : https://youtube.com/@OceanLabz
 ****************************************************/

 

// ==================== INCLUDES ====================
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <FS.h>
#include <SPI.h>
#include <SD.h>
#include <ArduinoJson.h>
#include <driver/i2s.h>
#include <ArduinoHttpClient.h>

// Audio libraries for MP3 decoding
#include "AudioFileSourceSD.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2S.h"

// ==================== DEFINES ====================
// SD Card pins
#define SD_CS       42
#define SD_MISO     46
#define SD_SCLK     2
#define SD_MOSI     3

// External DAC pins
#define I2S_BCLK  48
#define I2S_LRC   21
#define I2S_DOUT  47

// INMP441 Microphone pins
#define I2S_MIC_SERIAL_CLOCK     40     // SCK
#define I2S_MIC_LEFT_RIGHT_CLOCK 39     // WS
#define I2S_MIC_SERIAL_DATA      41     // SD

// Recording settings
#define RECORDING_DURATION 5            // seconds
#define SAMPLE_RATE 16000               // Hz
#define BUFFER_SIZE 1024

// ==================== CONFIGURATION ====================
// TODO: MOVE THESE TO secrets.h OR USE PREFERENCES/NVS
const char* ssid     = "YOUR SSID";
const char* password = "YOUR PASSWORD";

// TODO: REMOVE API KEYS FROM CODE - USE ENCRYPTED STORAGE
const char* openaiApiKey = "sk-proj-siqa2E7Qx-";
const char* elevenLabsApiKey = "sk_137a28120cd1594b4";

// API URLs
const char* openaiChatUrl = "https://api.openai.com/v1/chat/completions";
const char* openaiTtsUrl = "https://api.openai.com/v1/audio/speech";
const char* openaiSttUrl = "https://api.openai.com/v1/audio/transcriptions";
const char* elevenLabsTtsUrl = "https://api.elevenlabs.io/v1/text-to-speech/";

const char* voiceId = "21m00Tcm4TlvDq8ikWAM";

// ==================== GLOBALS ====================
AudioGeneratorMP3 *mp3 = nullptr;
AudioFileSourceSD *file = nullptr;
AudioOutputI2S *out = nullptr;

i2s_config_t i2s_mic_config;
i2s_pin_config_t i2s_mic_pins;

bool gettingResponse = false;
bool recordingMode = false;

/**
 * @brief Arduino setup function.
 *
 * Initializes Serial communication, SD card, WiFi connection,
 * I2S microphone configuration, and performs microphone diagnostics.
 * Also prints available user commands.
 */

void setup() {
  Serial.begin(115200);
  
  // Initialize SD card
  if (!setupSDCard()) {
    Serial.println("SD Card initialization failed!");
    while(1);
  }
  
  // Connect to WiFi
  connectToWiFi();
  
  // Initialize I2S microphone
  setupI2SMicrophone();
  
  // Run diagnostics
  testMicrophone();
  testMicrophoneDetailed();
  
  Serial.printf("Free heap: %d bytes\n", ESP.getFreeHeap());
  
  Serial.println("\n=== ESP32 Voice Assistant ===");
  Serial.println("Commands:");
  Serial.println("1. Type text and press Enter for TTS");
  Serial.println("2. Type 'RECORD' to start voice recording and STT");
  Serial.println("3. Type 'TTS:your text' for direct TTS");
  Serial.println("4. Type 'END' to finish text input");
}

/**
 * @brief Main Arduino loop.
 *
 * Handles MP3 audio playback, processes serial commands,
 * and keeps the system responsive.
 */

void loop() {
  // Handle audio playback
  if (mp3 && mp3->isRunning()) {
    if (!mp3->loop()) {
      mp3->stop();
      Serial.println("Playback finished");
      cleanupAudio();
    }
  }
  
  // Handle serial commands
  handleSerialCommands();
  
  delay(10);
}

/**
 * @brief Handles user commands received via Serial.
 *
 * Supports:
 * - RECORD command for voice input
 * - TTS:text for direct text-to-speech
 * - Plain text input for spoken output
 */

void handleSerialCommands() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n');
    command.trim();

    if (command.length() > 0) {
      if (command == "RECORD") {
        startRecordingAndTranscription();
      } else if (command.startsWith("TTS:")) {
        String ttsText = command.substring(4);
        ttsText.trim();
        if (ttsText.length() > 0) {
          generateAndPlayAudio(ttsText);
        } else {
          Serial.println("TTS text is empty. Use 'TTS:your text' format");
        }
      } else if (command != "END") {  // Regular text as TTS input
        Serial.print("TTS: ");
        Serial.println(command);
        generateAndPlayAudio(command);
      }
    }
  }
}

/**
 * @brief Generates speech audio from text and plays it.
 *
 * Sends text to the TTS API, saves the MP3 response to SD card,
 * and starts playback using the external I2S DAC.
 *
 * @param text Text to convert into speech
 */

void generateAndPlayAudio(String text) {
  Serial.println("Generating audio...");
  
  if (generateAudio(text)) {
    Serial.println("Audio generated successfully!");
    playAudioFromSD();
  } else {
    Serial.println("Failed to generate audio!");
  }
}

/**
 * @brief Generates MP3 audio using a cloud TTS API.
 *
 * Sends the given text to ElevenLabs (or OpenAI TTS),
 * downloads the generated MP3 audio stream,
 * and stores it on the SD card.
 *
 * @param text Text to convert to speech
 * @return true if audio generation succeeds, false otherwise
 */

bool generateAudio(String text) {
  HTTPClient http;
  
  // NOTE: Currently using ElevenLabs. Switch to OpenAI TTS if preferred
  String url = String(elevenLabsTtsUrl) + voiceId;
  
  http.begin(url);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("xi-api-key", elevenLabsApiKey);
  http.addHeader("Accept", "audio/mpeg");
  
  String jsonPayload = "{\"text\":\"" + escapeJsonString(text) + 
                      "\",\"model_id\":\"eleven_multilingual_v2\"," +
                      "\"voice_settings\":{\"stability\":0.5,\"similarity_boost\":0.5}}";
  
  int httpCode = http.POST(jsonPayload);
  


  if (httpCode == HTTP_CODE_OK) {
  int audioSize = http.getSize();
  
    if (audioSize > 0) {
      // Create unique filename
      String filename = "/audio_response.mp3";
      
      // Open file for writing
      File audioFile = SD.open(filename.c_str(), FILE_WRITE);
      if (!audioFile) {
        Serial.println("Failed to open file for writing");
        http.end();
        return false;
      }
      
      // Read audio data and write to file
      WiFiClient* stream = http.getStreamPtr();
      size_t bytesRead = 0;
      uint8_t buffer[1024];
      
      while (http.connected() && bytesRead < (size_t)audioSize) {
        size_t available = stream->available();
        if (available) {
          size_t toRead = min(available, sizeof(buffer));
          size_t read = stream->readBytes(buffer, toRead);
          if (read > 0) {
            audioFile.write(buffer, read);
            bytesRead += read;
          }
        }
        delay(1);
      }
      
      audioFile.close();
      Serial.printf("Audio saved: %s (%d bytes)\n", filename.c_str(), bytesRead);
      
      http.end();
      return true;
    }
  }
  else
  {
    Serial.printf("HTTP Error: %d\n", httpCode);
    Serial.println(http.getString());
  }
  
  http.end();
  return false;
}

/**
 * @brief Plays an MP3 audio file stored on the SD card.
 *
 * Initializes MP3 decoder and I2S output,
 * then streams audio to the external DAC.
 */


void playAudioFromSD() {
  String filename = "/audio_response.mp3";
  
  Serial.printf("Playing: %s\n", filename.c_str());
  
  file = new AudioFileSourceSD(filename.c_str());
  if (!file->isOpen()) {
    Serial.println("Failed to open MP3 file");
    cleanupAudio();
    return;
  }
  
  out = new AudioOutputI2S();
  out->SetPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
  out->SetGain(0.9);
  
  mp3 = new AudioGeneratorMP3();
  if (!mp3->begin(file, out)) {
    Serial.println("MP3 decoder begin failed");
    cleanupAudio();
    return;
  }
  
  Serial.println("Playback started");
}

/**
 * @brief Frees all audio-related resources.
 *
 * Stops playback and deletes MP3 decoder,
 * file source, and I2S output objects to prevent memory leaks.
 */

void cleanupAudio() {
  if (mp3) { delete mp3; mp3 = nullptr; }
  if (out) { delete out; out = nullptr; }
  if (file) { delete file; file = nullptr; }
}


/**
 * @brief Records microphone audio and converts speech to text.
 *
 * Records audio from the I2S microphone, saves it as a WAV file,
 * sends it to the Speech-to-Text API, and forwards the result
 * to ChatGPT for processing.
 */

void startRecordingAndTranscription() {
  Serial.println("\n----- Starting Recording -----");
  Serial.println("Please speak... (recording for " + String(RECORDING_DURATION) + " seconds)");

  const char* filename = "/recording.wav";
  
  if (recordAudioToSD(filename)) {
    Serial.println("Recording completed");
    Serial.println("Converting speech to text...");

    String transcribedText = speechToTextHttpClient(filename);
    
    // Optional: Delete recording after processing
    // SD.remove(filename);

    if (transcribedText.length() > 0) {
      Serial.println("\nRecognition result: " + transcribedText);
      Serial.println("\nSending to ChatGPT...");
      chatGptCall(transcribedText);
    } else {
      Serial.println("Failed to recognize text or an error occurred.");
    }
  } else {
    Serial.println("Failed to record audio!");
  }
}

/**
 * @brief Records audio from the INMP441 microphone to SD card.
 *
 * Captures I2S audio, converts it to 16-bit PCM WAV format,
 * applies basic noise gating, and stores the result on SD card.
 *
 * @param filename WAV file path on SD card
 * @return true if recording succeeds, false otherwise
 */

bool recordAudioToSD(const char* filename) {
  if (i2s_driver_install(I2S_NUM_0, &i2s_mic_config, 0, NULL) != ESP_OK) {
    Serial.println("Failed to install I2S driver");
    return false;
  }
  
  if (i2s_set_pin(I2S_NUM_0, &i2s_mic_pins) != ESP_OK) {
    Serial.println("Failed to set I2S pins");
    i2s_driver_uninstall(I2S_NUM_0);
    return false;
  }
  
  delay(100);
  
  if (SD.exists(filename)) {
    SD.remove(filename);
  }
  
  File wavFile = SD.open(filename, FILE_WRITE);
  if (!wavFile) {
    Serial.println("Failed to create WAV file");
    i2s_driver_uninstall(I2S_NUM_0);
    return false;
  }
  
  uint8_t wavHeader[44];
  uint32_t total_samples = SAMPLE_RATE * RECORDING_DURATION;
  createWavHeader(wavHeader, SAMPLE_RATE, 16, 1, total_samples);
  wavFile.write(wavHeader, 44);
  
  Serial.println("Recording... Speak now!");
  
  const size_t buffer_size = 64;
  int32_t audio_buffer[buffer_size];
  
  uint32_t samples_written = 0;
  unsigned long start_time = millis();
  uint32_t expected_samples = total_samples;
  
  while (samples_written < expected_samples) {
    size_t bytes_read = 0;
    
    esp_err_t result = i2s_read(I2S_NUM_0, 
                               (char*)audio_buffer, 
                               sizeof(audio_buffer), 
                               &bytes_read, 
                               100);
    
    if (result != ESP_OK && result != ESP_ERR_TIMEOUT) {
      Serial.println("I2S read error");
      break;
    }
    
    size_t samples_read = bytes_read / sizeof(int32_t);
    
    for (size_t i = 0; i < samples_read && samples_written < expected_samples; i++) {
      int32_t raw_sample = audio_buffer[i];
      int16_t sample_16bit = (int16_t)(raw_sample >> 11);  // Better precision
      
      // Noise gate
      if (abs(sample_16bit) < 200) {
        sample_16bit = 0;
      }
      
      wavFile.write((uint8_t*)&sample_16bit, sizeof(int16_t));
      samples_written++;
    }
    
    if (millis() - start_time > (RECORDING_DURATION * 1000 + 2000)) {
      break;
    }
  }
  
  wavFile.flush();
  
  if (samples_written > 0) {
    wavFile.seek(0);
    updateWavHeader(wavHeader, samples_written);
    wavFile.write(wavHeader, 44);
  }
  
  wavFile.close();
  i2s_driver_uninstall(I2S_NUM_0);
  
  Serial.printf("Recorded: %lu samples, File: %s\n", samples_written, filename);
  return samples_written > 0;
}

/**
 * @brief Converts recorded speech to text using Whisper API.
 *
 * Uploads a WAV audio file via HTTPS multipart request
 * and parses the transcription response.
 *
 * @param filename Path to WAV file on SD card
 * @return Transcribed text (empty string on failure)
 */

String speechToTextHttpClient(const char* filename) {
  String response = "";
  
  File audioFile = SD.open(filename, FILE_READ);
  if (!audioFile) {
    Serial.println("Failed to open audio file");
    return response;
  }
  
  WiFiClientSecure wifiClient;
  wifiClient.setInsecure();
  
  HttpClient client(wifiClient, "api.openai.com", 443);
  client.setHttpResponseTimeout(120000);
  
  String boundary = "----WebKitFormBoundary" + String(millis());
  String contentType = "multipart/form-data; boundary=" + boundary;
  
  size_t fileSize = audioFile.size();
  
  // Calculate content length
  String bodyStart = "--" + boundary + "\r\n";
  bodyStart += "Content-Disposition: form-data; name=\"file\"; filename=\"audio.wav\"\r\n";
  bodyStart += "Content-Type: audio/wav\r\n\r\n";
  
  String bodyEnd = "\r\n--" + boundary + "\r\n";
  bodyEnd += "Content-Disposition: form-data; name=\"model\"\r\n\r\n";
  bodyEnd += "whisper-1\r\n";
  bodyEnd += "--" + boundary + "--\r\n";
  
  size_t contentLength = bodyStart.length() + fileSize + bodyEnd.length();
  
  Serial.printf("Sending HTTPS request, size: %d bytes\n", contentLength);
  
  client.beginRequest();
  client.post("/v1/audio/transcriptions");
  client.sendHeader("Authorization", "Bearer " + String(openaiApiKey));
  client.sendHeader("Content-Type", contentType);
  client.sendHeader("Content-Length", contentLength);
  client.beginBody();
  
  client.print(bodyStart);
  
  const size_t chunkSize = 512;
  uint8_t buffer[chunkSize];
  size_t totalSent = 0;
  
  while (audioFile.available()) {
    size_t bytesRead = audioFile.read(buffer, chunkSize);
    if (bytesRead > 0) {
      client.write(buffer, bytesRead);
      totalSent += bytesRead;
      
      if (totalSent % 10240 == 0) {
        Serial.printf("Progress: %d/%d bytes (%.1f%%)\n", totalSent, fileSize, (totalSent * 100.0) / fileSize);
      }
    }
    delay(2);
  }
  
  client.print(bodyEnd);
  client.endRequest();
  
  Serial.println("Request sent, waiting for response...");
  
  int httpCode = client.responseStatusCode();
  String httpResponse = client.responseBody();
  
  Serial.print("HTTP Code: ");
  Serial.println(httpCode);
  
  if (httpCode == 200) {
    DynamicJsonDocument doc(2048);
    DeserializationError error = deserializeJson(doc, httpResponse);
    
    if (!error && doc.containsKey("text")) {
      response = doc["text"].as<String>();
      Serial.println("Transcription: " + response);
    } else {
      Serial.println("JSON parsing failed");
    }
  } else {
    Serial.print("Error response: ");
    Serial.println(httpResponse);
  }
  
  audioFile.close();
  return response;
}

/**
 * @brief Sends user input to ChatGPT and plays the response.
 *
 * Handles full AI interaction flow:
 * - Sends message to ChatGPT
 * - Prints response
 * - Converts response to speech
 *
 * @param message User input text
 */

void chatGptCall(String message) {
  Serial.println("Sending request to ChatGPT...");
  gettingResponse = true;

  String response = sendMessage(message);
  Serial.println(response);
  
  if (response != "") {
    Serial.print("ChatGPT: ");
    Serial.println(response);

    if (response.length() > 0) {
      Serial.println("Speaking recognized text...");
      generateAndPlayAudio(response);
    }
  } else {
    Serial.println("Failed to get ChatGPT response");
  }
  
  gettingResponse = false;
}

/**
 * @brief Sends a message to the OpenAI Chat Completion API.
 *
 * Builds the request payload, performs HTTP POST,
 * and returns the raw AI response.
 *
 * @param message User input text
 * @return ChatGPT response text
 */

String sendMessage(String message) {
  HTTPClient http;
  http.begin(openaiChatUrl);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", "Bearer " + String(openaiApiKey));

  String payload = buildChatGptPayload(message);
  int httpResponseCode = http.POST(payload);

  if (httpResponseCode == 200) {
    String response = http.getString();
    return processChatGptResponse(response);
  }
  
  Serial.printf("ChatGPT HTTP Error: %d\n", httpResponseCode);
  return "";
}

/**
 * @brief Builds a ChatGPT request JSON payload.
 *
 * Adds system instructions and user message
 * in the required OpenAI format.
 *
 * @param message User input text
 * @return Serialized JSON payload
 */

String buildChatGptPayload(String message) {
  DynamicJsonDocument doc(768);
  doc["model"] = "gpt-4.1-nano";  // Note: This model might not exist
  
  JsonArray messages = doc.createNestedArray("messages");
  
  JsonObject sysMsg = messages.createNestedObject();
  sysMsg["role"] = "system";
  sysMsg["content"] = "Please answer questions briefly, responses should not exceed 30 words.";

  JsonObject userMsg = messages.createNestedObject();
  userMsg["role"] = "user";
  userMsg["content"] = message;

  String output;
  serializeJson(doc, output);
  return output;
}

/**
 * @brief Extracts the assistant reply from ChatGPT JSON response.
 *
 * Parses the API response and returns clean text output.
 *
 * @param response Raw JSON response from ChatGPT
 * @return Extracted assistant message
 */

String processChatGptResponse(String response) {
  DynamicJsonDocument jsonDoc(1024);
  DeserializationError error = deserializeJson(jsonDoc, response);
  
  if (!error) {
    String outputText = jsonDoc["choices"][0]["message"]["content"];
    // Remove newlines for cleaner output
    outputText.replace("\n", " ");
    return outputText;
  }
  return "";
}


/**
 * @brief Creates a WAV file header.
 *
 * Generates a standard PCM WAV header based on
 * sample rate, bit depth, channel count, and sample size.
 *
 * @param header Buffer to store WAV header
 * @param sampleRate Audio sample rate
 * @param bitDepth Audio bit depth
 * @param channels Number of audio channels
 * @param numSamples Total number of samples
 */

void createWavHeader(uint8_t* header, uint32_t sampleRate, uint16_t bitDepth, uint16_t channels, uint32_t numSamples) {
  // RIFF header
  header[0] = 'R'; header[1] = 'I'; header[2] = 'F'; header[3] = 'F';
  uint32_t fileSize = numSamples * (bitDepth / 8) * channels + 36;
  header[4] = (fileSize) & 0xFF;
  header[5] = (fileSize >> 8) & 0xFF;
  header[6] = (fileSize >> 16) & 0xFF;
  header[7] = (fileSize >> 24) & 0xFF;
  header[8] = 'W'; header[9] = 'A'; header[10] = 'V'; header[11] = 'E';
  
  // fmt chunk
  header[12] = 'f'; header[13] = 'm'; header[14] = 't'; header[15] = ' ';
  header[16] = 16; header[17] = 0; header[18] = 0; header[19] = 0;
  header[20] = 1; header[21] = 0; // PCM format
  header[22] = channels & 0xFF; header[23] = (channels >> 8) & 0xFF;
  header[24] = sampleRate & 0xFF; 
  header[25] = (sampleRate >> 8) & 0xFF;
  header[26] = (sampleRate >> 16) & 0xFF; 
  header[27] = (sampleRate >> 24) & 0xFF;
  
  uint32_t byteRate = sampleRate * channels * (bitDepth / 8);
  header[28] = byteRate & 0xFF; 
  header[29] = (byteRate >> 8) & 0xFF;
  header[30] = (byteRate >> 16) & 0xFF; 
  header[31] = (byteRate >> 24) & 0xFF;
  
  header[32] = channels * (bitDepth / 8);
  header[33] = 0;
  header[34] = bitDepth; header[35] = 0;
  
  // data chunk
  header[36] = 'd'; header[37] = 'a'; header[38] = 't'; header[39] = 'a';
  uint32_t dataSize = numSamples * channels * (bitDepth / 8);
  header[40] = dataSize & 0xFF;
  header[41] = (dataSize >> 8) & 0xFF;
  header[42] = (dataSize >> 16) & 0xFF;
  header[43] = (dataSize >> 24) & 0xFF;
}

/**
 * @brief Updates WAV header after recording completes.
 *
 * Fixes file size and data chunk size fields
 * based on actual recorded samples.
 *
 * @param header WAV header buffer
 * @param actual_samples Number of recorded samples
 */

void updateWavHeader(uint8_t* header, uint32_t actual_samples) {
  uint32_t fileSize = actual_samples * 2 + 36;
  header[4] = fileSize & 0xFF;
  header[5] = (fileSize >> 8) & 0xFF;
  header[6] = (fileSize >> 16) & 0xFF;
  header[7] = (fileSize >> 24) & 0xFF;
  
  uint32_t dataSize = actual_samples * 2;
  header[40] = dataSize & 0xFF;
  header[41] = (dataSize >> 8) & 0xFF;
  header[42] = (dataSize >> 16) & 0xFF;
  header[43] = (dataSize >> 24) & 0xFF;
}

/**
 * @brief Configures I2S interface for INMP441 microphone.
 *
 * Sets sample rate, bit depth, channel format,
 * DMA buffers, and I2S pin mapping.
 */

void setupI2SMicrophone() {
  i2s_mic_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = 1024,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0
  };
  
  i2s_mic_pins = {
    .bck_io_num = I2S_MIC_SERIAL_CLOCK,
    .ws_io_num = I2S_MIC_LEFT_RIGHT_CLOCK,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = I2S_MIC_SERIAL_DATA
  };
  
  Serial.println("I2S Microphone configured for INMP441");
}

/**
 * @brief Performs a basic microphone functionality test.
 *
 * Listens for non-zero audio samples to verify
 * microphone wiring and signal presence.
 */

void testMicrophone() {
  Serial.println("\n=== Testing Microphone ===");
  
  if (i2s_driver_install(I2S_NUM_0, &i2s_mic_config, 0, NULL) != ESP_OK) {
    Serial.println("Failed to install I2S driver");
    return;
  }
  
  if (i2s_set_pin(I2S_NUM_0, &i2s_mic_pins) != ESP_OK) {
    Serial.println("Failed to set I2S pins");
    i2s_driver_uninstall(I2S_NUM_0);
    return;
  }
  
  delay(100);
  
  Serial.println("Listening for audio input... Speak into microphone!");
  
  int32_t sample;
  size_t bytes_read;
  unsigned long start = millis();
  int non_zero_count = 0;
  
  while (millis() - start < 3000) {
    if (i2s_read(I2S_NUM_0, (char*)&sample, sizeof(sample), &bytes_read, 100) == ESP_OK) {
      if (bytes_read == sizeof(sample)) {
        int16_t sample_16bit = (int16_t)(sample >> 8);
        if (abs(sample_16bit) > 100) {
          non_zero_count++;
        }
      }
    }
    delay(10);
  }
  
  if (non_zero_count > 0) {
    Serial.printf("Microphone test PASSED: %d audio events detected\n", non_zero_count);
  } else {
    Serial.println("Microphone test FAILED: No audio detected");
  }
  
  i2s_driver_uninstall(I2S_NUM_0);
}

/**
 * @brief Performs an advanced microphone diagnostic test.
 *
 * Reads raw I2S data, measures amplitude levels,
 * detects silent samples, and reports audio quality.
 */

void testMicrophoneDetailed() {
  Serial.println("\n=== Detailed Microphone Test ===");
  
  if (i2s_driver_install(I2S_NUM_0, &i2s_mic_config, 0, NULL) != ESP_OK) {
    Serial.println("Failed to install I2S driver");
    return;
  }
  
  if (i2s_set_pin(I2S_NUM_0, &i2s_mic_pins) != ESP_OK) {
    Serial.println("Failed to set I2S pins");
    i2s_driver_uninstall(I2S_NUM_0);
    return;
  }
  
  delay(500);
  
  Serial.println("I2S driver installed successfully");
  Serial.println("Reading raw I2S data for 5 seconds...");
  
  int32_t samples[100];
  size_t bytes_read;
  unsigned long start = millis();
  int total_samples = 0;
  int non_zero_samples = 0;
  int max_amplitude = 0;
  
  while (millis() - start < 5000) {
    if (i2s_read(I2S_NUM_0, (char*)samples, sizeof(samples), &bytes_read, 100) == ESP_OK) {
      int samples_read = bytes_read / sizeof(int32_t);
      total_samples += samples_read;
      
      for (int i = 0; i < samples_read; i++) {
        if (samples[i] != 0) {
          non_zero_samples++;
          int amplitude = abs(samples[i]);
          if (amplitude > max_amplitude) max_amplitude = amplitude;
        }
      }
    }
  }
  
  Serial.println("\n=== Test Results ===");
  Serial.printf("Total samples read: %d\n", total_samples);
  Serial.printf("Non-zero samples: %d\n", non_zero_samples);
  Serial.printf("Max amplitude: %d\n", max_amplitude);
  Serial.printf("Zero sample percentage: %.1f%%\n", 
                (total_samples - non_zero_samples) * 100.0 / total_samples);
  
  if (total_samples == 0) {
    Serial.println("CRITICAL: No samples read from I2S");
  } else if (non_zero_samples == 0) {
    Serial.println("PROBLEM: All samples are zero");
  } else if (max_amplitude < 1000) {
    Serial.println("WARNING: Very low amplitude detected");
  } else {
    Serial.println("SUCCESS: Audio data detected!");
  }
  
  i2s_driver_uninstall(I2S_NUM_0);
}

/**
 * @brief Initializes and mounts the SD card.
 *
 * Configures SPI interface, verifies SD card presence,
 * detects card type, and prints storage information.
 *
 * @return true if SD card is ready, false otherwise
 */

bool setupSDCard() {
  SPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS);
  
  if (!SD.begin(SD_CS)) {
    Serial.println("SD card mount failed!");
    return false;
  }
  
  uint8_t cardType = SD.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("No SD card attached");
    return false;
  }
  
  Serial.print("SD Card Type: ");
  if (cardType == CARD_MMC) Serial.println("MMC");
  else if (cardType == CARD_SD) Serial.println("SDSC");
  else if (cardType == CARD_SDHC) Serial.println("SDHC");
  else Serial.println("UNKNOWN");
  
  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);
  
  return true;
}

/**
 * @brief Connects ESP32 to a WiFi network.
 *
 * Attempts WiFi connection using configured credentials
 * and prints the assigned IP address.
 */

void connectToWiFi() {
  Serial.print("Connecting to WiFi");
  WiFi.begin(ssid, password);
  
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 20) {
    delay(500);
    Serial.print(".");
    attempts++;
  }
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("\nWiFi connected!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("\nWiFi connection failed!");
    while(1);
  }
}


/**
 * @brief Escapes special characters for JSON compatibility.
 *
 * Converts quotes, newlines, tabs, and backslashes
 * into valid JSON-safe sequences.
 *
 * @param input Raw input string
 * @return Escaped JSON-safe string
 */

String escapeJsonString(String input) {
  input.replace("\\", "\\\\");
  input.replace("\"", "\\\"");
  input.replace("\n", "\\n");
  input.replace("\r", "\\r");
  input.replace("\t", "\\t");
  return input;
}

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Need Help?