How to Build a Local Voice Assistant with Home Assistant (Wyoming, Whisper and Piper — No Alexa, No Cloud)

Alexa and Google Home send every utterance to a data centre and sell you things. Home Assistant's Assist does the same job locally: Whisper turns speech into text, Assist's intent engine (or a local LLM, 78) works out what you meant, Piper speaks the reply — all on the server, with openWakeWord listening for "okay nabu" on a satellite. The satellite can be the phone app, a Pi with a mic, or an ESP32-S3 box for £15 (the Home Assistant Voice PE if you'd rather buy one ready-made).
Quick reference
| Piece | What |
|---|---|
| Whisper (STT) | rhasspy/wyoming-whisper, port 10300 — base-int8 on CPU, small with a GPU |
| Piper (TTS) | rhasspy/wyoming-piper, port 10200 — voice en_GB-alan-medium |
| openWakeWord | rhasspy/wyoming-openwakeword, port 10400 |
| Home Assistant | Settings → Integrations → Wyoming ×3 → Settings → Voice assistants → Assist pipeline |
| Satellite | HA Companion app (phone), ESPHome voice satellite (123), or wyoming-satellite on a Pi |
| Expose devices | Settings → Voice assistants → Expose: lights, switches, covers, scripts |
1. The three containers
mkdir -p ~/docker/voice/{whisper,piper,wakeword} && cd ~/docker/voice
nano compose.yaml
services:
whisper:
image: rhasspy/wyoming-whisper:latest
container_name: whisper
command: --model base-int8 --language en --beam-size 1
ports:
- "10300:10300"
volumes:
- ./whisper:/data
restart: unless-stopped
piper:
image: rhasspy/wyoming-piper:latest
container_name: piper
command: --voice en_GB-alan-medium
ports:
- "10200:10200"
volumes:
- ./piper:/data
restart: unless-stopped
openwakeword:
image: rhasspy/wyoming-openwakeword:latest
container_name: openwakeword
command: --preload-model ok_nabu
ports:
- "10400:10400"
volumes:
- ./wakeword:/data
restart: unless-stopped
base-int8 runs a short command in about a second on an N100; tiny-int8 on a Pi 4; small/medium with a GPU for accuracy. en_GB-alan-medium is a decent British voice; Piper has dozens.
docker compose up -d
sudo ufw allow from 192.168.1.0/24 to any port 10200,10300,10400 proto tcp
2. Add them to Home Assistant
Settings → Devices & services → Add integration → Wyoming Protocol: host 192.168.1.50, port 10300 (Whisper). Again for 10200 (Piper) and 10400 (openWakeWord). Then Settings → Voice assistants → Add assistant: name "Home", language English, conversation agent Home Assistant, speech-to-text faster-whisper, text-to-speech piper, wake word ok_nabu. Set it as preferred.
3. Expose what it may control
Settings → Voice assistants → Expose: tick the lights, switches, covers, media players and scripts Assist may touch — and give them aliases ("the big lamp", "telly"). Assist understands "turn on the kitchen lights", "set the thermostat to 20", "is the garage door open", "turn off everything upstairs" (areas), timers ("set a timer for ten minutes"), and your own sentences via a YAML file or the UI for anything custom ("start the pump" → a script).
4. Talk to it
Phone: the Companion app → the Assist button (or assign it to the long-press action), speak, see the transcript and the reply, hear Piper. On Android it can be the default assistant.
A satellite on the shelf: an ESP32-S3-BOX-3 or an M5Stack Atom Echo (£15) flashed from the ESPHome dashboard (123) with the voice-assistant project — it listens for the wake word (on the server via openWakeWord, or on-device for the S3), streams audio to HA, plays the reply. The Home Assistant Voice Preview Edition is the ready-made version. A Pi with a USB mic and speaker runs wyoming-satellite for a room with a bigger speaker (Snapcast's rooms, 154, can play the responses).
5. Make it smarter
Assist's built-in intents are deliberate and local. For "what's the weather like for a bike ride tomorrow" or "add the things I just said to the shopping list", set the conversation agent to a local LLM: Ollama (78) with a model like llama3.2 or qwen2.5:7b via HA's Ollama integration, with Assist control enabled so it can still run device commands. Latency rises to a few seconds on CPU; a GPU makes it feel instant. Cloud models work too, but then it isn't local — the whole point.
6. Tune
- Whisper: --beam-size 1 for speed; a bigger model if it mishears names.
- Piper: try en_GB-cori, en_GB-jenny_dioco; speed via --length-scale.
- Wake word: hey_jarvis, alexa (the open model), or train your own with the openWakeWord notebook.
- Noise: satellites with echo cancellation (the S3 Box has it) in rooms with music.
- Everything stays on the LAN; put the containers' ports behind UFW to the HA host only if HA runs elsewhere.
Troubleshooting
Assist says "unexpected error" on speech. Whisper not reachable from HA (host networking in HA → use the LAN IP), or the model still downloading — docker compose logs whisper.
It hears but does nothing. The device isn't exposed, or the name doesn't match; try the alias. The debug view under Voice assistants shows each step.
Slow. A too-large Whisper model on CPU; base-int8, beam-size 1.
Wake word triggers constantly / never. Sensitivity in the openWakeWord integration options; mic gain on the satellite.
Wrapping up
Three containers, three Wyoming integrations, one Assist pipeline, the devices exposed with friendly aliases, a phone or a £15 box as the ears. Lights, heating, timers and your own scripts by voice, with every word processed on your server — and a local model behind it when you want conversation rather than commands.
