Offline Voice Assistant on Raspberry Pi OS Trixie¶
Build a local voice pipeline in testable stages: microphone capture, voice activity or keyword detection, speech-to-text, a strict intent dispatcher, and text-to-speech. Raspberry Pi OS Trixie can run this on 64-bit ARM without sending recordings to a cloud service, but model size, microphone quality, and cooling determine the actual latency.
This guide uses the actively maintained sherpa-onnx project as the common runtime because it supports ARM64 Linux, Raspberry Pi, streaming and non-streaming recognition, keyword spotting, VAD, and speech synthesis. It does not use the retired pattern of installing the old Google Assistant SDK or copying OAuth credentials into a generic script.
Architecture and trust boundaries¶
Treat every recognised phrase as untrusted input. Do not pass it to shell=True, eval, a shell pipeline, sudo, or an unrestricted home-automation API. The dispatcher should map a small known phrase to a fixed argument list.
Choose the first useful version¶
| Version | Components | Use it when |
|---|---|---|
| Push-to-talk prototype | Record button, ASR, printed response | Proving the microphone and model |
| Endpointed assistant | VAD, ASR, allowlisted actions, TTS | A desk or workshop assistant |
| Wake-word assistant | Keyword spotter, VAD, ASR, actions, TTS | Hands-free use after false-positive testing |
| Conversational assistant | Above plus a local or remote language model | You have measured latency, privacy, and failure behavior |
Start with push-to-talk. A wake word adds a continuously running model, false accepts, false rejects, and more audio-device state. An LLM adds nondeterministic output and should not be allowed to create shell commands.
Hardware and OS baseline¶
Recommended starting point:
- Raspberry Pi 5 or Pi 4 with a 64-bit Raspberry Pi OS image
- USB microphone or a supported audio HAT
- USB, HDMI, HAT, or analogue audio output appropriate to the model
- official-quality power supply and active cooling for sustained inference
- at least several gigabytes of free storage for environments, models, logs, and recordings used during testing
Record the system before installing anything:
Expect aarch64 from uname -m for the ARM64 Python wheel path. Do not infer userspace architecture from the board model alone.
Understand Raspberry Pi audio layers¶
Raspberry Pi OS Desktop normally exposes applications through PipeWire-compatible interfaces, while Raspberry Pi OS Lite can use ALSA directly. The official Raspberry Pi audio guidance warns that a global ~/.asoundrc can interfere with the desktop's view of devices; create one only for an application that truly needs direct ALSA configuration.
List devices without changing configuration:
On a desktop session, also inspect the user audio server when the commands exist:
Do not run the assistant as root to “fix” microphone access. A root process has a different environment and creates a much larger impact if recognition triggers the wrong action.
Install dependencies in a virtual environment¶
Install OS packages:
Create an application directory owned by your normal user:
On 6 August 2026, PyPI published sherpa-onnx ARM64 Linux wheels for current CPython versions including 3.13. Always verify the package source, resolved version, and wheel architecture on the device instead of copying that observation indefinitely:
Do not use sudo pip, and do not disable Debian's externally-managed-environment protection. Recreate the virtual environment when changing Python major versions.
Prove capture and playback first¶
Record five seconds as 16 kHz, 16-bit, mono PCM. Replace the device after checking arecord -l:
Inspect the signal without uploading it:
Listen for clipping, hum, echo, and automatic-gain pumping. Software cannot recover words that the input clipped or never captured clearly. Move the microphone, reduce speaker feedback, and set a repeatable distance before comparing models.
Select and verify an offline model¶
sherpa-onnx supports multiple model families. Choose one from its official pre-trained model documentation that explicitly lists:
- supported language
- streaming or non-streaming operation
- sample rate
- model licence
- required tokens, encoder, decoder, or joiner files
- CPU and memory expectations
Avoid copying a model URL from an unrelated tutorial. Download into a versioned directory and keep a checksum manifest:
The locally generated checksum detects later corruption but does not authenticate the first download. Compare publisher-provided hashes when available and record each source URL and licence.
Useful official entry points:
- sherpa-onnx documentation
- real-time microphone example
- text-to-speech models
- embedded Linux and ALSA guidance
Use the command shown by the chosen model page because argument names differ between transducer, Paraformer, Whisper, SenseVoice, Moonshine, and other model families. Confirm available options in the installed version:
Build a safe intent dispatcher¶
Test command policy separately from speech recognition. Create dispatcher.py:
Test accepted and rejected input:
The last input must be rejected. subprocess.run() receives a fixed list and never invokes a shell. Keep write operations, door locks, alarms, purchases, and destructive administration out of the first version.
Connect recognition to the dispatcher¶
The recognition callback should pass only the final recognised text to dispatch(). Keep partial transcripts for UI feedback, not actions. Use an explicit state machine:
Required safeguards:
- Discard empty and partial results.
- Limit maximum utterance duration.
- Require exact phrases for privileged or state-changing actions.
- Add a physical or spoken confirmation for actions with consequences.
- Apply a cooldown so echo from the speaker cannot immediately retrigger the assistant.
- Log the action identifier and outcome, not raw audio by default.
Do not ask a language model to return a command that is then executed. If you later add conversational generation, place it outside the deterministic action boundary.
Add keyword spotting only after ASR works¶
sherpa-onnx includes keyword spotting, and openWakeWord is another local option. Model licences and commercial-use terms differ, so review both code and model licences.
Measure at least:
- true activations in quiet speech
- missed activations at intended distances
- false activations during television, music, and conversation
- CPU and memory use while idle
- time from keyword end to listening state
Keep a visible microphone or listening indicator. Add a hardware mute switch that electrically or logically disables capture when privacy matters.
Add offline text-to-speech¶
sherpa-onnx provides ARM and ARM64 speech-synthesis packages and model-specific examples. Keep TTS separate from ASR so each stage can be timed and replaced.
Test generated audio before integrating it:
Follow the exact command on the selected model page, write to a WAV file, then play it through the verified device. Do not let recognised text become a filename or command-line option without validation.
Prevent speaker output from retriggering recognition by pausing capture during playback or using echo cancellation with a measured cooldown.
Run as an unprivileged user service¶
First make the complete assistant loop work interactively. Then create ~/.config/systemd/user/offline-voice.service:
The example assumes assistant_loop.py already exists and writes only to logs. A desktop user service can access the user's audio session more naturally than a system service. Raspberry Pi OS Lite may need different ordering because it can use ALSA directly and have no PipeWire user service.
Validate before enabling:
Enable it only after an interactive start succeeds:
Measure latency and resource use¶
Do not publish model performance without measurements from the stated hardware. Record timestamps at:
Monitor the process:
Use a result table with real values:
| Pi / cooling | Model | Audio | Endpoint-to-text | Response start | CPU | RSS | Notes |
|---|---|---|---|---|---|---|---|
| Record your system | Record exact files | 16 kHz mono | — | — | — | — | Do not fill with estimates |
Run enough repeated phrases to show median and slow-tail behavior. Include misses and false activations; averages alone hide failures.
Troubleshooting¶
PortAudioError: Error querying device¶
List devices from the same user and environment that runs the assistant:
Do not assume device index 0 remains stable after adding HDMI, USB, Bluetooth, or HAT audio. Prefer a verified device name where the API supports it.
Works in a terminal but not under systemd¶
Compare the user, environment, working directory, audio session, and model paths. Use absolute paths and inspect the user journal. Do not solve it by switching to a root system service.
Recognition is slow¶
Check throttling and cooling, then compare a smaller or quantised model from the same official family. Measure endpointing separately from inference; waiting for silence can look like slow recognition.
Recognition is inaccurate¶
Replay the saved test WAV through the recogniser. If the recording itself is clipped, quiet, reverberant, or dominated by speaker echo, fix capture before changing models.
The ARM64 package does not install¶
Verify all three layers:
Recreate the virtual environment after confirming aarch64/arm64. Do not install an AMD64 wheel or use --break-system-packages.
The assistant triggers itself¶
Pause recognition during TTS, lower speaker-to-microphone coupling, add a post-playback cooldown, and test echo cancellation. Keep the action dispatcher idempotent where possible.
Privacy and maintenance checklist¶
- process audio locally unless the interface clearly indicates a cloud mode
- do not retain raw recordings by default
- protect logs from transcripts, tokens, addresses, and personal names
- verify model and package licences before distribution or commercial use
- pin and record working dependency versions, but review security updates
- keep actions allowlisted and unprivileged
- provide a visible listening state and physical mute option
- test failure after network loss, audio-device removal, model corruption, and reboot
- back up configuration, not captured speech
FAQ¶
Can this work without Internet access?¶
Yes, after OS packages, Python wheels, and model files are installed. Test by disconnecting the network and confirming that recognition, dispatch, and speech generation still work.
Is Raspberry Pi 5 required?¶
No. Smaller streaming models can run on older 64-bit Pi models, but latency and model choice must be measured. Pi Zero-class systems need especially careful model and audio choices.
Should I use Vosk instead?¶
Vosk remains an offline recogniser with small models suitable for Raspberry Pi. Its current public Python package and documentation should be tested against the exact Trixie Python version. This guide uses sherpa-onnx because its current ARM64 wheels and documentation cover ASR, VAD, keyword spotting, and TTS in one actively updated project.
Can a local LLM control shell commands?¶
Do not execute generated shell text. Map confirmed intents to fixed, reviewed argument lists outside the model, and require additional confirmation for consequential actions.