ESP32 Bluetooth HFP Voice Agent (Advanced Build)

ESP32 Bluetooth HFP Voice Agent (Advanced Build)

An ESP32 Bluetooth HFP voice agent answers a phone call over Bluetooth Hands-Free Profile, streams the audio to a WiFi-connected AI server, and plays synthesized speech back through the same link. It is one of the harder builds here: original ESP32 silicon only, careful WiFi/Bluetooth coexistence, and firmware reaching past Arduino's usual libraries into ESP-IDF's Bluetooth internals.

Quick answer: Use the original ESP32 (WROOM-32 chip, Bluetooth Classic) — never an S3, C3, or C6, which are BLE-only and cannot run HFP. WiFi and Bluetooth Classic share one radio here, so call audio and server streaming need real coexistence tuning. This guide ships a firmware starting point, not a proven call-audio pipeline — said plainly, upfront.

Proof of work: we built this exact session through Soldr, Compoden's AI build assistant, on 10 August 2026 — every screenshot below is from that session, including the parts still visible where we, honestly, do not have a finished answer. Published 11 August 2026 · Last updated 11 August 2026.

What parts do you need for a Bluetooth HFP voice agent?

The session's own choice is a bare ESP32-WROOM-32 module (Rs.280) plus an I2S microphone, an I2S speaker amp, and — because that exact module has no onboard USB — a separate USB-to-serial programmer to flash it. Here is the tray as it was actually built, plus what it costs to add the parts the firmware guide names but the session never carded.

Parts list for the HFP voice agent (prices checked August 2026)
Part Qty Price (Rs.) Where to buy
ESP32-WROOM-32 (bare MCU module — the board the session used) 1 280 ESP32-WROOM-32
CP2102 USB 2.0 to TTL UART Serial Converter (required — see note below) 1 226 CP2102 USB 2.0 to TTL UART Serial Converter
INMP441 I2S MEMS Microphone Module 1 240 INMP441 I2S MEMS Microphone Module
MAX98357A I2S Class-D speaker amp (named in the session's flash guide) 1 [HANDLE-NOT-FOUND] Not currently in the Compoden catalog — source separately
3W 8 Ohm Speaker 1 95 3W 8 Ohm Speaker
SYB-170 Mini Breadboard (170 points) 1 20 SYB-170 Mini Breadboard
Dupont Jumper Wire Kit (M-M, M-F, F-F, 20cm, 22AWG) 1 60 Dupont Jumper Wire Kit
Total (catalog parts, excl. the un-carded amp) 921 Cash on delivery available across India

The honest math on "the exact board I want": the bare ESP32-WROOM-32 module costs Rs.280, less than the ESP32-DevKitC V4 (30-pin CP2102) Soldr recommended first, at Rs.400. But the bare module — per its own Compoden catalog listing — "has no onboard USB-serial programmer, voltage regulator, or reset/boot buttons," and is described as "NOT a drop-in prototyping choice." Add the Rs.226 USB-to-TTL converter you now need just to flash it, and the total is Rs.506 — more than the Rs.400 dev board that already includes all of that on one PCB. Insisting on the exact named part cost more here, not less. If you don't already own a dev board and don't need the bare module for a custom PCB, buy the DevKitC V4 instead and skip both the Rs.226 line and this whole workaround (linked in the buying-guidance section below).

Tools: Arduino IDE with the ESP32 core installed (3.x), a laptop, USB cables for the board and the USB-TTL converter, and a phone that supports Bluetooth HFP (basically any modern smartphone).

Step 1: Ask Soldr for a board and a starting point

We described the whole build in one prompt, engineering constraints included. Our exact prompt was: "I want to an AI voice agent that connects to a phone over Bluetooth HFP (Hands-Free Profile) - it needs to answer/handle calls, capture and stream the call audio, and play back synthesized speech through the same HFP link. The board also needs to talk to a server (WiFi) for the actual AI processing. Recommend a board and give me the wiring + firmware starting point."

Soldr recommends the original ESP32 WROOM-32 for Bluetooth HFP and warns ESP32-S3 and C3 are BLE-only and cannot run HFP
Proof from our 10 August 2026 session: Soldr's 35-second answer names the exact chip family the build needs, and states the WiFi/Bluetooth radio-sharing constraint before any parts are locked in.

Soldr's answer, in 35 seconds: "The correct platform is an original ESP32 board with the ESP32-WROOM-32 chip — not an ESP32-S3/C3. It is the hobbyist ESP32 family option with Bluetooth Classic HFP, while WiFi handles the server connection; S3/C3 boards are BLE-only and cannot provide phone-call HFP." It then named the real constraint up front — this is the load-bearing engineering fact of the whole build: "WiFi and Bluetooth Classic share the ESP32's radio, so simultaneous call audio and server streaming needs careful buffering and coexistence tuning." It also declined to over-promise on audio hardware: "Use an external audio codec or HFP audio interface only after confirming the exact board variant and library support; the ESP32's built-in audio paths are not a complete phone-grade analog interface by themselves."

It then asked a clarifying question instead of guessing: "Do you want the original ESP32 WROOM-32 board as the base, or are you constrained to a particular board you already have?" — with three choices offered (ESP32 WROOM-32, Existing board, Let you decide). At this point the parts tray held two real candidates: the ESP32-DevKitC V4 (30-pin CP2102) at Rs.400 and the bare ESP32-WROOM-32 module at Rs.280 — Soldr had not yet committed to either (both are linked in the parts list and the buying-guidance section below).

Checkpoint: Soldr has named the correct chip family, flagged the radio-sharing constraint, and is waiting on you to pick a specific board before generating any wiring or code.

Step 2: The user overrides the recommendation — what actually changed

Rather than tap a suggested button, we typed a specific instruction: "use the original ESP32 WROOM-32 - that is the board I want." This is the moment worth narrating honestly, because Soldr complied literally — and that literal compliance has a real cost that it did not spell out.

Soldr's spec sheet for the bare ESP32-WROOM-32 module after the user overrides to that exact board: 48 pins, 80mA draw
Proof from the session: the parts tray collapsed to a single item — the bare ESP32-WROOM-32 module — and Soldr's 15-second reply justified the pick on specs alone, without mentioning the module's missing onboard USB-serial converter.

The parts tray dropped from two boards to one: the ESP32-DevKitC V4 disappeared, leaving only the bare ESP32-WROOM-32 module (Rs.280) marked Controller. Soldr's justification was entirely spec-based: "This is the right choice because you specifically requested the original ESP32 WROOM-32, and it provides both wireless capability and a broad set of control interfaces in one part." The only tradeoff it surfaced was price: "if price is the deciding factor, the customer can pick a different board from the options shown."

Two things it did not say, which we checked against the part's own catalog listing after the fact: this exact module has no onboard USB-serial programmer, voltage regulator, or reset/boot buttons — the catalog description calls it explicitly "NOT a drop-in prototyping choice" — and Soldr's own build profile for this pick still read "Difficulty: Beginner (every part plugs in, no soldering)," unchanged from the two-board recommendation. A bare SoC module that needs an external programmer to flash and a full HFP+I2S+WiFi firmware stack is not a beginner build by any reasonable measure; the difficulty label reads like it didn't get re-evaluated when the parts tray changed underneath it.

This is not a story about Soldr being wrong to comply — you asked for the exact original ESP32 WROOM-32, and that is what it gave you, correctly matched by chip family and Bluetooth Classic support. It is a story about a named part carrying assumptions that don't automatically travel with it. See the parts table above for the real cost of this choice.

Checkpoint: parts tray shows exactly one item — ESP32-WROOM-32, Rs.280 — and you understand you'll also need the Rs.226 USB-TTL converter from the parts list to program it.

Step 3: Ask for the full firmware

Our third prompt was four words: "give me the full firmware code." This build ran long — the build log was still reading "Designing your build" and "compiling…" more than 237 seconds in when we captured the session, well past the 15-74 second turnaround we saw on the simpler builds earlier in this ladder. HFP-plus-I2S-plus-WiFi firmware is a heavier codegen job than a blinking LED, and it showed.

Soldr's flash guide for the HFP voice agent: I2S mic/amp pins, SERVER_URL placeholder, Arduino IDE settings
Proof from the session: the flash guide names the exact hardware the sketch expects — INMP441 mic on GPIO36, MAX98357A amp near GPIO25/26 — and is upfront that the AI-server audio link is a placeholder to fill in.

While it worked, Soldr summarized what the (still-generating) sketch expected: "The sketch expects an external I2S microphone (INMP441) on GPIO36 (data) and an I2S speaker amp (MAX98357A) on GPIO25/26/… adapt pins as needed. The HFP will advertise as 'ESP32 AI Voice Agent'; pair your phone, and calls will be detected. Audio streaming to your AI server is a placeholder — replace SERVER_URL with your actual endpoint and implement the playback fetch logic. Libraries needed: none beyond the ESP32 core." The one-line flash guide it gave: install Arduino ESP32 core 3.x, select board "ESP32 Dev Module," set Flash Size to "4MB (32Mb)," and use partition scheme "Default 4MB with spiffs" if you use NVS. The log also showed "⚠ caught 1 hardware problem in that sketch — fixing…" before compiling — we don't know what that problem was, since only the summary line was captured, not the diff.

Honest gap: the literal generated code sits in the session's Firmware tab, which this capture set does not include — only the description above and the flash guide were visible. The pin note itself trails off ("GPIO25/26/…") rather than giving a complete pinout. The code block below is therefore a reference scaffold built to match every fact quoted above, not a copy-paste of Soldr's literal output. Treat it as the starting point it's named for, and verify pin numbers against your own session.

Checkpoint: you can see the flash guide, the named hardware (INMP441, MAX98357A), the SERVER_URL placeholder callout, and "Libraries needed: none beyond the ESP32 core" in your own session's build log.

The full code (firmware starting point)

This scaffold brings up WiFi, both I2S audio paths, and the ESP-IDF Bluedroid Hands-Free Client so the board advertises, pairs, and reports call state — matching every hardware fact Soldr's flash guide gave. It deliberately leaves the hardest part undone: bridging live call audio to your AI server and back. Soldr's own Step-1 answer flagged that piece as needing confirmed board/library support before wiring in a codec, and nothing in this session closed that loop. Board: ESP32 Dev Module · Flash Size: 4MB (32Mb) · Arduino ESP32 core 3.x · no extra libraries beyond the core.

// ESP32 Bluetooth HFP Voice Agent -- firmware STARTING POINT, not a finished build.
// Board: ESP32 Dev Module (Arduino IDE) - ESP32 core 3.x - Flash Size: 4MB (32Mb)
// Partition scheme: "Default 4MB with SPIFFS" if you use NVS.
// Hardware named in the session's flash guide: INMP441 I2S mic (data on GPIO36),
// MAX98357A I2S speaker amp (near GPIO25/26 -- the session's own summary trails
// off with "..." here, so confirm the remaining I2S pins against your own build).
// Libraries needed: none beyond the ESP32 Arduino core (per the session) -- HFP
// itself is reached through the ESP-IDF Bluedroid Hands-Free Client API
// (esp_hf_client_api.h), which ships inside the ESP32 core but is NOT wrapped by
// Arduino's higher-level BluetoothSerial or A2DP-style libraries. This is the
// advanced part of this build: calling into IDF Bluetooth internals from a
// sketch, not just including a library.

#include <WiFi.h>
#include <driver/i2s.h>
extern "C" {
  #include "esp_bt.h"
  #include "esp_bt_main.h"
  #include "esp_bt_device.h"
  #include "esp_gap_bt_api.h"
  #include "esp_hf_client_api.h"
}

// ---- fill in for your build ----
const char* WIFI_SSID       = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD   = "YOUR_WIFI_PASSWORD";
const char* SERVER_URL      = "https://your-ai-server.example.com/stream"; // placeholder -- session's own words: "replace SERVER_URL with your actual endpoint and implement the playback fetch logic"
const char* HFP_DEVICE_NAME = "ESP32 AI Voice Agent"; // exact name from the session's flash guide

// ---- I2S pins: only the mic data pin is explicit in the session; the rest are
//      standard INMP441 / MAX98357A wiring -- verify against your own build ----
#define I2S_MIC_WS    25   // typical INMP441 word-select pin (not named in session)
#define I2S_MIC_SCK   26   // typical INMP441 bit-clock pin (not named in session)
#define I2S_MIC_SD    36   // GPIO36 -- the one pin the session's flash guide names explicitly
#define I2S_SPK_BCLK  27   // amp pins given as "GPIO25/26/..." (truncated) -- adapt to your wiring
#define I2S_SPK_LRC   14
#define I2S_SPK_DIN   12

void connectWiFi() {
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected: " + WiFi.localIP().toString());
}

void initMicI2S() {
  i2s_config_t cfg = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = 16000,                          // HFP narrowband; use 16000 if WBS/mSBC negotiated
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,   // INMP441 outputs 24-bit data in a 32-bit frame
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags = 0,
    .dma_buf_count = 4,
    .dma_buf_len = 256,
  };
  i2s_pin_config_t pins = {
    .bck_io_num = I2S_MIC_SCK,
    .ws_io_num = I2S_MIC_WS,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = I2S_MIC_SD,                      // GPIO36, as named in the session
  };
  i2s_driver_install(I2S_NUM_0, &cfg, 0, NULL);
  i2s_set_pin(I2S_NUM_0, &pins);
}

void initSpeakerI2S() {
  i2s_config_t cfg = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
    .sample_rate = 16000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags = 0,
    .dma_buf_count = 4,
    .dma_buf_len = 256,
  };
  i2s_pin_config_t pins = {
    .bck_io_num = I2S_SPK_BCLK,
    .ws_io_num = I2S_SPK_LRC,
    .data_out_num = I2S_SPK_DIN,
    .data_in_num = I2S_PIN_NO_CHANGE,
  };
  i2s_driver_install(I2S_NUM_1, &cfg, 0, NULL);
  i2s_set_pin(I2S_NUM_1, &pins);
}

// HFP client event callback -- this is where an incoming call, an answered
// call, and the audio-connect event all land. Filling this in for real call
// handling and the SERVER_URL bridge is the work this scaffold leaves undone.
void hfClientCallback(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t *param) {
  switch (event) {
    case ESP_HF_CLIENT_CONNECTION_STATE_EVT:
      Serial.println("HFP connection state changed");
      break;
    case ESP_HF_CLIENT_AUDIO_STATE_EVT:
      Serial.println("HFP audio state changed -- call audio path is live/down");
      // TODO: start/stop streaming mic frames to SERVER_URL and playing back
      // synthesized speech here. Session's own note: "Audio streaming to your
      // AI server is a placeholder -- replace SERVER_URL with your actual
      // endpoint and implement the playback fetch logic."
      break;
    default:
      break;
  }
}

void initBluetoothHFP() {
  esp_bt_controller_config_t btCfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
  esp_bt_controller_init(&btCfg);
  esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);   // Classic BT only -- HFP is a Classic profile
  esp_bluedroid_init();
  esp_bluedroid_enable();
  esp_bt_dev_set_device_name(HFP_DEVICE_NAME);        // advertises as "ESP32 AI Voice Agent"
  esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
  esp_hf_client_register_callback(hfClientCallback);
  esp_hf_client_init();
}

void setup() {
  Serial.begin(115200);
  connectWiFi();
  initMicI2S();
  initSpeakerI2S();
  initBluetoothHFP();
  Serial.println("Pair your phone with \"" + String(HFP_DEVICE_NAME) + "\" now.");
}

void loop() {
  // This scaffold brings up WiFi, both I2S paths, and the HFP client -- the
  // actual call-audio <-> WiFi-server bridge (buffering, mSBC/CVSD codec
  // handling, the SERVER_URL fetch/playback loop) is the part Soldr's own
  // Step-1 answer flagged as needing confirmed board/library support before
  // wiring in an external codec. That logic is not in this starting point.
  delay(1000);
}

Which exact board should you buy? What Soldr actually said

Our fourth prompt asked exactly this: "which exact board should I buy from you and why that one?" — and here is the honest state of that turn: our capture shows the question typed into the box while the firmware build from Step 3 was still compiling (237+ seconds in, "Building" still greyed out). We do not have Soldr's literal answer to this question — the session moved on before that reply was captured, and we are not going to invent one and attribute it to Soldr.

The buying-guidance question typed into Soldr while the prior firmware build was still compiling, unanswered in this capture
Proof from the session, unedited: the fourth prompt sits typed in the input box with the send control still reading "Building" — the firmware turn above it hadn't finished, so no answer to this question was captured.

What we can give you is the buying guidance the session's own data supports, stated as our own recommendation rather than a quote:

  • If you're prototyping and want it working fastest: buy the ESP32-DevKitC V4 (30-pin CP2102), Rs.400. It has the same ESP32-WROOM-32 chip and the same Bluetooth Classic HFP support Soldr identified as mandatory in Step 1 — it just also has the onboard CP2102 USB-serial converter, voltage regulator, and reset/boot buttons the bare module lacks. This was Soldr's own first recommendation before the override.
  • If you specifically need the bare module — for a custom PCB, a production run, or because that's what you already have — buy the ESP32-WROOM-32 module (Rs.280, linked in the parts list above) plus the CP2102 USB 2.0 to TTL UART Serial Converter (Rs.226, also in the parts list) to flash it. Total: Rs.506 — more than the dev board above, for less convenience. Same chip either way; the difference is entirely in what's soldered around it.

Checkpoint: you've picked one of the two boards above with your eyes open about what each one does and doesn't include.

Does it work? The honest state of this build

No — and we're saying that plainly rather than implying otherwise. This session, as captured, gets you: a correctly identified chip family, a board actually wired into the parts tray, a firmware scaffold that brings up WiFi, both I2S audio paths, and the HFP client stack, and an honest list of what's still missing. It does not get you a phone call answered by an AI voice on a bench in front of us. This is a firmware starting point for one of the hardest builds in this series, not a proven, working call-audio pipeline — exactly what the original prompt asked for ("give me... a firmware starting point"), and exactly what we're delivering here.

What's still open, in order of how much work each one is: (1) the call-audio-to-SERVER_URL bridge — buffering, the actual AI request/response cycle, and playback timing; (2) the WiFi/Bluetooth Classic coexistence tuning Soldr flagged in Step 1, which only shows up under load, not at compile time; (3) confirming the mSBC/CVSD codec path HFP negotiates with your specific phone; and (4) the remaining I2S pin assignments the session's own summary trailed off before finishing.

Troubleshooting: real failure modes for this build

Why did Soldr rule out the ESP32-S3 or C3 for this build?

Because Bluetooth HFP is a Classic Bluetooth profile, and the S3, C3, and C6 are BLE-only chips with no Classic Bluetooth radio at all — no library or code change fixes that, only swapping the board does. This matches what our separate "What Is Bluetooth Classic?" session found independently (linked in "what to build next" below): among hobbyist ESP32 boards, only the original WROOM-32 family carries Classic Bluetooth (and with it, SPP, A2DP, and HFP); the newer-looking S3/C3/C6 boards cannot run any of the three, however capable they look on paper.

Why would call audio glitch even after everything compiles?

Because WiFi and Bluetooth Classic share the ESP32's single radio, as Soldr's Step-1 answer stated directly — streaming call audio to your server over WiFi while HFP is active on the same chip needs deliberate buffering and coexistence tuning, not just two features that happen to both be "on." This class of bug shows up as intermittent, load-dependent audio dropouts, not a compile error, which makes it easy to miss until you're testing with a real call.

Why won't my board show up when I try to flash it?

If you bought the bare ESP32-WROOM-32 module, this is expected until you add the external USB-TTL converter — the module's own catalog listing states it has no onboard USB-serial programmer. Connect the CP2102 converter's TX/RX/GND (and hold GPIO0 low during flashing, per the module's boot-mode requirement) rather than looking for a USB port on the module itself, because there isn't one.

Why does compiling take so long for this build?

Because it's genuinely more code than the earlier builds in this ladder. Our own capture shows the build log still reading "compiling…" past 237 seconds, well beyond the 15-74 second range we measured on this series' simpler projects — HFP-plus-I2S-plus-WiFi is a heavier codegen job, and Soldr's own log reflects that rather than hiding it.

What should you build next?

This is the advanced end of our project ladder — if Bluetooth HFP wasn't your first stop, it shouldn't be. Start with your first Arduino project: a button-controlled LED, then a soil moisture monitor with OLED display, then an ESP32 temperature and humidity dashboard on your phone. A line-follower robot build sits between those and this one on the ladder (queued). And before you pick a board for any Bluetooth build, read What Is Bluetooth Classic? Wiring and Errors — the S3-vs-original-ESP32 distinction that drives this entire post.

Or start your own build right now: open Soldr, Compoden's AI build assistant, describe your build in one sentence — including the real engineering constraints, like we did — and see what tradeoffs it surfaces for your exact parts.

Frequently asked questions

ESP32 par Bluetooth HFP voice agent banane ke liye kaunsa board sahi hai?

Original ESP32 (WROOM-32 chip) chahiye — ESP32-S3, C3, ya C6 nahi, kyunki woh BLE-only hain aur HFP (phone call ka Bluetooth Classic profile) support nahi karte. Prototyping ke liye ESP32-DevKitC V4 (Rs.400, onboard USB) behtar hai; bare WROOM-32 module (Rs.280) ke saath alag se USB-TTL converter (Rs.226) chahiye hoga.

Can the ESP32-S3 or C3 run Bluetooth HFP?

No. As Soldr's session stated directly: S3/C3 boards are BLE-only and cannot provide phone-call HFP. HFP is a Classic Bluetooth profile, and among common hobbyist ESP32 boards, only the original WROOM-32 family has a Classic Bluetooth radio at all.

Is the code in this post the actual firmware Soldr generated?

No, and we're not claiming it is. The session's literal generated code lives in its Firmware tab, which our screenshot capture does not include — only the description and flash guide were visible. The code block above is a reference scaffold built to match every hardware fact Soldr's own flash guide stated (pins, device name, placeholder, required Arduino settings), not a copy of its literal output.

How much does this build cost in India?

Rs.921 for the catalog parts we could verify — the bare ESP32-WROOM-32 module, a USB-TTL programmer to flash it, an INMP441 I2S microphone, a 3W 8 Ohm speaker, a breadboard, and jumper wires. The MAX98357A I2S speaker amp the firmware guide names is not currently in the Compoden catalog and needs to be sourced separately. Prices checked August 2026; cash on delivery is available across India.

Does this guide end with a working, phone-answering AI voice agent?

No — and this post says so directly rather than implying otherwise. What you get is a correctly-identified board, a firmware scaffold covering WiFi, I2S audio, and the HFP client stack, and an honest list of the coexistence tuning and audio-bridging work still required. It is the firmware starting point the original prompt asked for, not a finished, bench-verified call.

Back to blog