A multi-model Muse: personal AI voice assistant on ESP32 - wake word or knob, any OpenAI-compatible brain (STT/chat/TTS mix-and-match), streaming replies, out of the box. Built on muse-gadget-sdk.
Shell
6
13 commits
updated Oct 15, 2026
A multi-model Muse — a personal AI assistant on ESP32, talking out of the box.
Think of it as a Muse that thinks with your models: say "Hi, ESP" (or press the knob), ask anything, and it answers out loud — streaming sentence-by-sentence while the model is still writing, in English or Chinese, on a $50 gadget with a mic, a speaker and a round display.
No vendor lock on the brain: speech, transcription and chat are three independent endpoints, each pointed at any OpenAI-compatible server — one provider for all three, or a different one per task.
Cloud assistants rent you a brain: one vendor's models, one vendor's pricing, one vendor's uptime, and your voice on their servers. The gadget hardware is fine — the lock is in the firmware.
esp32-thinks flips that. The device runs your stack: pick the cheapest transcription, the smartest chat model, the voice you like — per task, changeable in one config line and a rebuild. The whole pipeline is ~1,600 lines of patch against a stock open-source SDK, so it stays yours to read, tweak, or point at a local server.
openai, quicksilver, custom.| Device | Seeed SenseCAP Watcher W1-B (ESP32-S3, 1.45" 412×412 round LCD, ES8311 speaker codec, ES7243E mic, PTT knob) — more boards the SDK supports are candidates |
| Base firmware | muse-gadget-sdk @ 812c46f (2026-10-09) + patches/muse-brain.patch (see what's theirs, what's ours) |
| ESP-IDF | v6.0.1 (esp32s3 target) |
| Brain | Any OpenAI-compatible servers: STT (/audio/transcriptions), chat (/chat/completions, SSE streaming), speech (/audio/speech) |
| Presets | openai (all three) · quicksilver (deepseek-v4.1-flash + whisper-large-v3-turbo + chatterbox/qwen3-tts) · custom |
| Wake word | esp-sr WakeNet10 wn10_hiesp ("Hi, ESP", on-chip, model flash partition) — custom phrases trainable via Espressif's TTS Pipeline V3 |
| Cost | ~fractions of a cent per reply (QuickSilver TTS $1.5–25/1M chars, OpenAI $15/1M) |
| Build host | Linux or macOS with ESP-IDF v6.x |
# 0) One-time: ESP-IDF v6.x for esp32s3 (docs/REFERENCE.md §Prerequisites)
git clone https://github.com/facebookincubator/muse-gadget-sdk.git
git clone https://github.com/raullenchai/esp32-thinks.git
cd esp32-thinks
# 1) Back up the factory NVS once — your restore path if anything goes sideways
./scripts/backup-nvs.sh ../muse-gadget-sdk /dev/ttyACM1
# 2) Apply the patch + configure your brain (pick a preset)
./apply.sh ../muse-gadget-sdk --preset quicksilver # or openai / custom
# 3) Build + flash (CH342-safe pacing, ~2 min)
./build-and-flash.sh ../muse-gadget-sdk /dev/ttyACM1
With a preset + headless Wi-Fi in step 2, that's the whole setup: unplug, re-plug, and talk — press the knob, or just say "Hi, ESP".
| Command | Does |
|---|---|
./scripts/backup-nvs.sh <sdk> [port] | Dump the factory NVS partition (0x9000, 0x32000) — do this once |
./apply.sh <sdk> [--preset ...] | Copy pinned CAs, apply patches/muse-brain.patch, generate provider_config.h (asks for keys, Wi-Fi, wake word) |
./build-and-flash.sh <sdk> [port] | idf.py build + paced flash (firmware + wake-word model); never interrupt it |
python3 scripts/tts_probe.py <sdk> [port] ["question"] | End-to-end test over the serial console |
Credit where it's due — and clarity about the split:
Theirs (upstream, untouched behavior): the device framework, LVGL UI and round-LCD drivers, audio stack (codec, resampler, minimp3), input/console plumbing, OTA and partitioning. This recipe targets the SDK at a pinned commit so patches apply cleanly.
Ours (patches/muse-brain.patch, ~1,600 lines): the brain session (muse_chat_session.cpp — three-endpoint config, SSE sentence streaming, TTS pipeline, keep-alive TLS), the wake-word integration (muse_voice.c — direct WakeNet10 on idle chunks, wake→PTT synthesis, VAD end-of-turn, cooldown), the model flash partition and the esp-dl kernel fix, plus the tooling (apply.sh, build-and-flash.sh, NVS backup, probes).
Nothing else from the Muse cloud is required: the vendor agent works as an optional fallback (its token is optional), and with headless provisioning the device never contacts it.
Everything lives in esp32/components/muse/provider_config.h (written by apply.sh):
| Setting | What |
|---|---|
TTS_URL / TTS_MODEL / TTS_VOICE | Speech endpoint, model, voice |
TTS_MODEL_CJK | Optional second speech model, auto-selected for Chinese turns |
TTS_API_KEY / TTS_CA | Its key and pinned root (OPENAI_CA or ISRG_CA) |
BRAIN_STT_URL / BRAIN_STT_MODEL | Transcription endpoint (shares the chat key) |
BRAIN_CHAT_URL / BRAIN_CHAT_KEY / BRAIN_MODEL | Chat endpoint; leave the URL empty to use the Muse cloud agent |
BRAIN_CHAT_EXTRA | Extra JSON merged into the chat body (provider quirks, e.g. DeepSeek's reasoning.exclude) |
BRAIN_SYSTEM | The assistant's persona / system prompt |
WAKE_WORD | 1 = "Hi, ESP" wake word + button; 0 = button only |
WAKE_VAD_DB | End-of-turn silence threshold for wake turns (raise toward -35 if it keeps recording after you stop) |
DEV_WIFI_SSID / DEV_WIFI_PASS | Headless provisioning; empty = normal BLE setup |
Changed a define? Re-run ./build-and-flash.sh — it rebuilds and reflashes.
.!? and CJK 。!?…), so Chinese works too.Latency receipts (warm, measured on device, QuickSilver preset): transcription 3.4 s, first spoken word ≈ 5–7 s after you stop; the text caption shows at ~2–3 s. Cold adds ~1.5 s. See REFERENCE for the full breakdown and every gotcha.
provider_config.h (it's generated; apply.sh keeps it out of the repo).files/*.pem); the ESP cert bundle alone doesn't carry them (the v0.1 story — see REFERENCE — now with a second root for Let's Encrypt hosts).Full story, exact code paths and every gotcha found the hard way: docs/REFERENCE.md.
Top three (full checklist in REFERENCE):
brain heard: (transcription worked) and TTS ok: — a BRAIN STT FAILED in a quiet room is normal (whisper returns empty); in a real conversation it means the key/URL is wrong.device reports readiness to read but returned no data — two processes fighting over /dev/ttyACM1 (monitor + probe). One at a time.esptool at high baud. The CH342 bridge drops whole packets; the recipe uses paced_esptool at 115200 for a reason.A multi-model Muse: personal AI voice assistant on ESP32 - wake word or knob, any OpenAI-compatible brain (STT/chat/TTS mix-and-match), streaming replies, out of the box. Built on muse-gadget-sdk.
Shell
6
13 commits
updated Oct 15, 2026
A multi-model Muse — a personal AI assistant on ESP32, talking out of the box.
Think of it as a Muse that thinks with your models: say "Hi, ESP" (or press the knob), ask anything, and it answers out loud — streaming sentence-by-sentence while the model is still writing, in English or Chinese, on a $50 gadget with a mic, a speaker and a round display.
No vendor lock on the brain: speech, transcription and chat are three independent endpoints, each pointed at any OpenAI-compatible server — one provider for all three, or a different one per task.
Cloud assistants rent you a brain: one vendor's models, one vendor's pricing, one vendor's uptime, and your voice on their servers. The gadget hardware is fine — the lock is in the firmware.
esp32-thinks flips that. The device runs your stack: pick the cheapest transcription, the smartest chat model, the voice you like — per task, changeable in one config line and a rebuild. The whole pipeline is ~1,600 lines of patch against a stock open-source SDK, so it stays yours to read, tweak, or point at a local server.
openai, quicksilver, custom.| Device | Seeed SenseCAP Watcher W1-B (ESP32-S3, 1.45" 412×412 round LCD, ES8311 speaker codec, ES7243E mic, PTT knob) — more boards the SDK supports are candidates |
| Base firmware | muse-gadget-sdk @ 812c46f (2026-10-09) + patches/muse-brain.patch (see what's theirs, what's ours) |
| ESP-IDF | v6.0.1 (esp32s3 target) |
| Brain | Any OpenAI-compatible servers: STT (/audio/transcriptions), chat (/chat/completions, SSE streaming), speech (/audio/speech) |
| Presets | openai (all three) · quicksilver (deepseek-v4.1-flash + whisper-large-v3-turbo + chatterbox/qwen3-tts) · custom |
| Wake word | esp-sr WakeNet10 wn10_hiesp ("Hi, ESP", on-chip, model flash partition) — custom phrases trainable via Espressif's TTS Pipeline V3 |
| Cost | ~fractions of a cent per reply (QuickSilver TTS $1.5–25/1M chars, OpenAI $15/1M) |
| Build host | Linux or macOS with ESP-IDF v6.x |
# 0) One-time: ESP-IDF v6.x for esp32s3 (docs/REFERENCE.md §Prerequisites)
git clone https://github.com/facebookincubator/muse-gadget-sdk.git
git clone https://github.com/raullenchai/esp32-thinks.git
cd esp32-thinks
# 1) Back up the factory NVS once — your restore path if anything goes sideways
./scripts/backup-nvs.sh ../muse-gadget-sdk /dev/ttyACM1
# 2) Apply the patch + configure your brain (pick a preset)
./apply.sh ../muse-gadget-sdk --preset quicksilver # or openai / custom
# 3) Build + flash (CH342-safe pacing, ~2 min)
./build-and-flash.sh ../muse-gadget-sdk /dev/ttyACM1
With a preset + headless Wi-Fi in step 2, that's the whole setup: unplug, re-plug, and talk — press the knob, or just say "Hi, ESP".
| Command | Does |
|---|---|
./scripts/backup-nvs.sh <sdk> [port] | Dump the factory NVS partition (0x9000, 0x32000) — do this once |
./apply.sh <sdk> [--preset ...] | Copy pinned CAs, apply patches/muse-brain.patch, generate provider_config.h (asks for keys, Wi-Fi, wake word) |
./build-and-flash.sh <sdk> [port] | idf.py build + paced flash (firmware + wake-word model); never interrupt it |
python3 scripts/tts_probe.py <sdk> [port] ["question"] | End-to-end test over the serial console |
Credit where it's due — and clarity about the split:
Theirs (upstream, untouched behavior): the device framework, LVGL UI and round-LCD drivers, audio stack (codec, resampler, minimp3), input/console plumbing, OTA and partitioning. This recipe targets the SDK at a pinned commit so patches apply cleanly.
Ours (patches/muse-brain.patch, ~1,600 lines): the brain session (muse_chat_session.cpp — three-endpoint config, SSE sentence streaming, TTS pipeline, keep-alive TLS), the wake-word integration (muse_voice.c — direct WakeNet10 on idle chunks, wake→PTT synthesis, VAD end-of-turn, cooldown), the model flash partition and the esp-dl kernel fix, plus the tooling (apply.sh, build-and-flash.sh, NVS backup, probes).
Nothing else from the Muse cloud is required: the vendor agent works as an optional fallback (its token is optional), and with headless provisioning the device never contacts it.
Everything lives in esp32/components/muse/provider_config.h (written by apply.sh):
| Setting | What |
|---|---|
TTS_URL / TTS_MODEL / TTS_VOICE | Speech endpoint, model, voice |
TTS_MODEL_CJK | Optional second speech model, auto-selected for Chinese turns |
TTS_API_KEY / TTS_CA | Its key and pinned root (OPENAI_CA or ISRG_CA) |
BRAIN_STT_URL / BRAIN_STT_MODEL | Transcription endpoint (shares the chat key) |
BRAIN_CHAT_URL / BRAIN_CHAT_KEY / BRAIN_MODEL | Chat endpoint; leave the URL empty to use the Muse cloud agent |
BRAIN_CHAT_EXTRA | Extra JSON merged into the chat body (provider quirks, e.g. DeepSeek's reasoning.exclude) |
BRAIN_SYSTEM | The assistant's persona / system prompt |
WAKE_WORD | 1 = "Hi, ESP" wake word + button; 0 = button only |
WAKE_VAD_DB | End-of-turn silence threshold for wake turns (raise toward -35 if it keeps recording after you stop) |
DEV_WIFI_SSID / DEV_WIFI_PASS | Headless provisioning; empty = normal BLE setup |
Changed a define? Re-run ./build-and-flash.sh — it rebuilds and reflashes.
.!? and CJK 。!?…), so Chinese works too.Latency receipts (warm, measured on device, QuickSilver preset): transcription 3.4 s, first spoken word ≈ 5–7 s after you stop; the text caption shows at ~2–3 s. Cold adds ~1.5 s. See REFERENCE for the full breakdown and every gotcha.
provider_config.h (it's generated; apply.sh keeps it out of the repo).files/*.pem); the ESP cert bundle alone doesn't carry them (the v0.1 story — see REFERENCE — now with a second root for Let's Encrypt hosts).Full story, exact code paths and every gotcha found the hard way: docs/REFERENCE.md.
Top three (full checklist in REFERENCE):
brain heard: (transcription worked) and TTS ok: — a BRAIN STT FAILED in a quiet room is normal (whisper returns empty); in a real conversation it means the key/URL is wrong.device reports readiness to read but returned no data — two processes fighting over /dev/ttyACM1 (monitor + probe). One at a time.esptool at high baud. The CH342 bridge drops whole packets; the recipe uses paced_esptool at 115200 for a reason.