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_wordrecord_promptperform_sttgenerate_responsestream_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.pyopen(...) calls risk leaking handles on error. Fix with with 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.