The Problem
Building a voice assistant that is fully local and private is awkward. Cloud APIs (Alexa, Google Home) send audio off-device and require accounts; self-hosted stacks scatter the components across STT, LLM, and TTS servers. This repo demonstrates a working alternative: a Raspberry Pi Zero 2 W acting as a thin client, with all heavy inference running on a homelab server over LAN. The result is a functional HAL 9000 replica with no cloud dependency.
What This Does
app.py is the entry point, orchestrating a wake-word-triggered conversation loop. It uses Porcupine for local wake-word detection on the Pi, then records audio, sends it to a Vosk STT server, passes the transcript to an Ollama-hosted llama3 model, and streams the reply back through a Piper TTS server with a custom-trained HAL voice (the .onnx model lives in models/porcupine/).
lights.py and drivers/apa102.py control the HAL eye's status LEDs, providing visual feedback (fade-in/out, color cycling) during listening, processing, and speaking states. The assets/ folder holds build photos, and requirements.txt lists seven Python dependencies.
How It Is Wired
Execution starts at app.py, which defines the full pipeline: listen_for_wake_word → record_prompt → perform_stt → generate_response → stream_tts. Each function makes a network call to a self-hosted service on the LAN; the repo does not show the server-side code, so the exact protocol (HTTP/WebSocket) is not mapped here.
The internal call graph shows lights.py is the hub: set_pixel is called from 3 places, show from 2. drivers/apa102.py is the low-level LED driver, with __init__ and write defining the hardware interface. app.py imports lights.py, which imports the driver, so a change to drivers/apa102.py affects the entire LED stack.
A significant gap: there is no error handling or retry logic visible between app.py and the external services. A network failure on any hop will crash the pipeline.
How To Use It
The README documents the full build, but the repo itself lacks a Dockerfile or a lockfile, so setup is manual:
# On the Raspberry Pi
pip install -r requirements.txt
python app.py
Configuration is not centralized. The README implies the STT/LLM/TTS server addresses are hardcoded or environment-specific, but no .env file or config module exists. You will need to edit app.py to point at your homelab server's IP and ports. The models/porcupine/ directory contains the wake-word model, which is architecture-specific (Raspberry Pi).
Real-World Use
This fits a homelab with an always-on server. The Pi Zero 2 W idles at low power, and the server handles all compute. A typical flow: say "Hey Hal," the Pi records, sends audio to Vosk, gets a transcript, sends it to Ollama, receives a response, sends it to Piper, and plays the audio—all over LAN.
Code Health & Issues
Static analysis (4 findings, all medium) reports:
- Medium – File opened without context manager (x2) –
drivers/apa102.py,app.py–open(...)calls risk leaking handles on error. Fix withwith open(...). - Medium – Deep nesting (x2) –
drivers/apa102.py,app.py– max indentation depth of 6 makes control flow hard to follow. Flatten with early returns.
SDLC observations: no tests, no CI, no lockfile. The repo is a single-developer prototype; treat it as a reference implementation, not production software.
The Bottom Line
A well-documented, working proof-of-concept for a fully local voice assistant. The hardware build is the star; the code is minimal and functional but lacks error handling, tests, and configuration management. Use it as a starting point for your own build, but expect to harden the Python before relying on it.