Claudia
Build your own always-on voice assistant in an afternoon — a Raspberry Pi Zero 2 WH with the Hiwonder WonderEcho voice module, wired straight to the Claude API. Sits on your shelf, listens for "Claudia", and Claude answers out loud in seconds. No Alexa account, no surveillance, no subscription — just a Claude API key and hardware you own.
WH, not W. The WonderEcho connects to four GPIO pins (SDA / SCL / 5V / GND), so the build needs the WH variant with pre-soldered headers. Buying the plain "W" means soldering 40 pins yourself before anything works.
Before you start, gather: a Windows / macOS / Linux computer to flash the microSD and SSH in, a way to plug a microSD into it (the SanDisk Ultra ships with a full-size SD adapter but no USB reader — most modern ultrabooks and MacBooks need a USB microSD reader, ~$8), and a 2.4 GHz Wi-Fi network (the Pi Zero 2 WH has no 5 GHz radio). The smart-plug options below ship with US plugs; each vendor (Kasa, Shelly, Sonoff) also sells EU/UK/AU variants that speak the same local API — pick your region at checkout. No soldering iron needed, and no extra jumper wires — the WonderEcho ships with the 4-pin Dupont cable already.
Stock check. The Pi Zero 2 WH is supply-constrained; if all the US retailers on the cards below show out-of-stock, rpilocator.com tracks live availability across the official reseller network.
Last updated: 2026.05.23f
01. Configure #
02. Shopping list #
03. Assemble #
Total time: ~3 minutes. No soldering.
- Do not insert the microSD yet. Flash it first in section 04.
- Connect the WonderEcho to the Pi's I²C header pins via the 4-pin Dupont cable that ships in the WonderEcho box (Hiwonder includes it — you should not need to source one separately):
SDA → BCM 2 (pin 3),SCL → BCM 3 (pin 5),5V → pin 2,GND → pin 6. - Make sure the WonderEcho's speaker face is unobstructed (it doubles as the mic intake).
- Snap the PiSugar 3 battery onto the underside of the Pi using its magnetic pogo pins. No soldering — the spring-loaded pogo pins align themselves.
Final stack: WonderEcho (via I²C cable) ←→ Pi Zero 2 WH → PiSugar 3
Final layout: WonderEcho (via I²C cable) ←→ Pi Zero 2 WH (wall-powered)
✅ Checkpoint: The four I²C wires are seated firmly, nothing wobbles, the WonderEcho's speaker grille is unobstructed.
04. Flash microSD #
4.1 Install Raspberry Pi Imager #
If your laptop has no SD-card slot — common on recent ultrabooks and every modern MacBook — plug in a USB microSD reader now. The card itself ships with a full-size SD adapter, but that only helps you if the host has a full-size SD slot.
Download from raspberrypi.com/software (Windows, macOS, Linux).
4.2 Flash #
- Open Raspberry Pi Imager.
- Choose Device →
Raspberry Pi Zero 2 W(Imager doesn't distinguish W from WH — the OS image is the same). - Choose OS →
Raspberry Pi OS (other)→ Raspberry Pi OS (64-bit) (the full version, not Lite).- The chatbot repo's install script expects packages from the full image. Lite will work but you'll need extra apt installs and may hit surprises.
- Choose Storage → your microSD card.
- Click the gear icon (⚙) for Edit Settings and configure:
- Hostname:
claudia - Username: anything other than
pi— Pi OS Bookworm deprecated the defaultpiuser, and current Imager builds warn (or refuse) when you try to set it. Useclaudia, your first name, or any other identifier you'll remember. - Password: something secure
- Enable SSH: ✅ password auth
- Wireless LAN: SSID + password for your home Wi-Fi
- Locale: your timezone (e.g.
America/Chicago), keyboard your layout (e.g.us)
- Hostname:
- Save, then Write. Takes 2–5 minutes.
4.3 First boot #
Insert the microSD into the Pi.
Plug the official power supply into the
PWR INmicro-USB port (the one nearest the corner, labeledPWR INon the silkscreen). Not the middle port labeledUSB.Wait 60–90 seconds.
From your PC:
ssh <your-username>@claudia.localIf
claudia.localdoesn't resolve, find the Pi's IP in your router's admin page and usessh <your-username>@192.168.x.x.
✅ Checkpoint: You see the <your-username>@claudia:~ $ prompt. Run cat /etc/os-release and confirm it says Debian/Raspberry Pi OS. Run free -h — you should see ~430 MB of Mem: (the Pi Zero 2 WH has 512 MB total).
05. System setup #
Run these from the SSH session. One at a time. Wait for each to finish.
5.1 Update #
sudo apt update && sudo apt full-upgrade -y
This takes 5–15 minutes on a Pi Zero 2 WH. Be patient.
5.2 Free up RAM (Pi Zero only has 512 MB) #
The Pi Zero 2 WH is RAM-constrained. Disable services you don't need:
# Disable Bluetooth (not used by this build)
sudo systemctl disable hciuart bluetooth
# Disable triggerhappy (gamepad daemon, not needed)
sudo systemctl disable triggerhappy
5.3 Install build dependencies #
sudo apt install -y git curl build-essential python3-pip python3-venv \
portaudio19-dev libsndfile1 ffmpeg alsa-utils libatlas-base-dev
5.4 Enable I²C and detect the WonderEcho #
The WonderEcho is an I²C device. Turn the bus on, install i2c-tools, then verify the module answers on the bus.
# Enable I²C non-interactively
sudo raspi-config nonint do_i2c 0
# Tools + Python bindings
sudo apt install -y i2c-tools python3-smbus
sudo reboot
After it reboots, SSH back in and run:
i2cdetect -y 1
You should see a device address show up (commonly 0x52 for the WonderEcho — verify against the sticker on the module).
✅ Checkpoint: i2cdetect -y 1 lists at least one device address — the WonderEcho is talking to the Pi.
06. Install chatbot #
This is the PiSugar whisplay-ai-chatbot repo — we use it as the LLM/ASR/TTS plumbing even though we're not using the Whisplay HAT itself. Wake-word and audio I/O go through the WonderEcho instead.
cd ~
git clone https://github.com/PiSugar/whisplay-ai-chatbot.git
cd whisplay-ai-chatbot
bash install_dependencies.sh
source ~/.bashrc
The dependency install pulls Node.js, Python packages, and audio libraries. This takes 15–25 minutes on a Pi Zero 2 WH. Let it finish.
The
source ~/.bashrcline is important — the installer sets PATH entries you need in your current shell session.
✅ Checkpoint: install_dependencies.sh finishes without errors. Test that Node is on PATH:
node --version
You should see v20.x or newer (upstream's installer pulls in the current Node LTS).
07. API key #
- Go to console.anthropic.com and sign in (or create an account).
- Add a payment method and put a small amount of credit on the account (e.g., $5 — that lasts a long time on Haiku).
- Navigate to API Keys → Create Key.
- Name it
claudia. Copy the key now — you can't see it again later. - Treat the key like a password.
Approximate cost: Casual personal use on claude-haiku-4-5-20251001 typically runs a few dollars per month at most. Check current pricing at anthropic.com/pricing.
Which model to pick #
| Model ID | Speed | Quality | When to use |
|---|---|---|---|
claude-haiku-4-5-20251001 |
Fastest | Good | Default for this device. Latency matters more than essay-grade prose for a voice assistant. |
claude-sonnet-4-6 |
Medium | Excellent | If you want richer answers and don't mind a slightly slower response. |
claude-opus-4-7 |
Slowest | Best | Overkill for spoken Q&A. Use for hard reasoning tasks only. |
Model IDs change over time. The current list lives at docs.claude.com.
08. Configure chatbot #
8.1 Create your .env #
cd ~/whisplay-ai-chatbot
cp .env.template .env
nano .env
The template ships with many fields for different ASR/LLM/TTS providers. For a Claude-based build, you need the LLM section set to Anthropic. Find and set:
# === LLM (the AI brain) ===
LLM_SERVER=anthropic
ANTHROPIC_API_KEY=sk-ant-YOUR-KEY-HERE
ANTHROPIC_MODEL=claude-haiku-4-5-20251001
# === System prompt — shapes the assistant's voice ===
SYSTEM_PROMPT=You are a concise, friendly voice assistant. Answer in plain spoken English — no markdown, no bullet lists, no headings. Keep responses to 1–3 sentences unless the user explicitly asks for more.
The wake-word listener does not run on the Pi — it's handled in hardware by the WonderEcho (see section 08.3 below). The Pi only polls the WonderEcho's wake-event register over I²C, so no WAKE_WORD_* env keys are needed.
Env-key naming: upstream uses
LLM_SERVER,ASR_SERVER,TTS_SERVER(not*_PROVIDER). The plugin registry switches on the lowercase value — seesrc/cloud-api/server.tsin the upstream repo.
ASR (speech-to-text): Whisper, local. Already wired up by the template defaults. Slowest option on a Pi Zero 2 WH (~3–6 s per utterance) but no API key required and works offline.
ASR (speech-to-text): OpenAI Whisper API. Add to your .env:
ASR_SERVER=openai
OPENAI_API_KEY=sk-REPLACE-ME
Round-trip latency drops to ~0.5–1 s. Costs a few cents per hour of speech.
ASR (speech-to-text): Google Cloud STT. Add to your .env:
ASR_SERVER=google
GOOGLE_APPLICATION_CREDENTIALS=/home/pi/google-stt-key.json
Drop the service-account JSON from Google Cloud Console at the path above. Generally fastest cloud STT on US-region traffic.
TTS (text-to-speech): Piper, local. Free, runs on the Pi. Voice quality is "robot but understandable" — fine for short replies. Add to your .env:
TTS_SERVER=piper
PIPER_BINARY_PATH=/usr/local/bin/piper
PIPER_MODEL_PATH=/home/pi/piper/voices/en_US-amy-low.onnx
TTS (text-to-speech): OpenAI gpt-4o-mini-tts (recommended). Near-state-of-the-art quality, supported by upstream out-of-the-box. Add to your .env:
TTS_SERVER=openai
OPENAI_API_KEY=sk-REPLACE-ME
OPENAI_VOICE_MODEL=gpt-4o-mini-tts
OPENAI_VOICE_TYPE=nova
The new gpt-4o-mini-tts model and the 4o-series voices (alloy, nova, onyx, marin, cedar, plus older echo/fable/shimmer/ash/ballad/coral/sage/verse) are dramatically more natural than the older tts-1. Costs roughly $0.015 per minute of speech.
TTS (text-to-speech): ElevenLabs (best quality, requires a one-time patch).
ElevenLabs has the most natural voices on the market right now, but the upstream chatbot doesn't ship an ElevenLabs handler. You add one yourself — about 40 lines of TypeScript and a single registration entry.
Step 1 — handler. Create ~/whisplay-ai-chatbot/src/cloud-api/elevenlabs/elevenlabs-tts.ts with:
import mp3Duration from "mp3-duration";
import { TTSResult } from "../../type";
// The chatbot already loads .env at startup, so process.env is populated
// by the time this plugin's activate() runs — no need to call dotenv here.
const apiKey = process.env.ELEVENLABS_API_KEY || "";
const voiceId = process.env.ELEVENLABS_VOICE_ID || "EXAVITQu4vr4xnSDxMaL"; // "Bella"
const modelId = process.env.ELEVENLABS_MODEL_ID || "eleven_turbo_v2_5"; // low-latency
const stability = parseFloat(process.env.ELEVENLABS_STABILITY || "0.5");
const similarity = parseFloat(process.env.ELEVENLABS_SIMILARITY || "0.75");
const elevenLabsTTS = async (text: string): Promise<TTSResult> => {
if (!apiKey) { console.error("ELEVENLABS_API_KEY is not set."); return { duration: 0 }; }
const url = `https://api.elevenlabs.io/v1/text-to-speech/${encodeURIComponent(voiceId)}`;
let res: Response;
try {
res = await fetch(url, {
method: "POST",
headers: {
"xi-api-key": apiKey,
"Content-Type": "application/json",
"Accept": "audio/mpeg",
},
body: JSON.stringify({
text,
model_id: modelId,
voice_settings: { stability, similarity_boost: similarity },
}),
});
} catch (e) {
console.log("ElevenLabs TTS request failed:", e);
return { duration: 0 };
}
if (!res.ok) {
console.log("ElevenLabs TTS HTTP " + res.status + ": " + (await res.text().catch(() => "")));
return { duration: 0 };
}
const buffer = Buffer.from(await res.arrayBuffer());
const duration = await mp3Duration(buffer);
// mp3-duration returns undefined if it can't parse the stream; coerce to
// 0 so downstream code never sees NaN.
return { buffer, duration: (duration ?? 0) * 1000 };
};
export default elevenLabsTTS;
Step 2 — register the plugin. Open ~/whisplay-ai-chatbot/src/plugin/builtin/tts.ts and add this block alongside the other pluginRegistry.register(...) calls:
pluginRegistry.register({
name: "elevenlabs",
displayName: "ElevenLabs TTS",
version: "1.0.0",
type: "tts",
audioFormat: "mp3",
description: "ElevenLabs text-to-speech (high-quality cloud voices)",
activate: () => {
const ttsProcessor = require("../../cloud-api/elevenlabs/elevenlabs-tts").default;
return { ttsProcessor };
},
} as TTSPlugin);
Step 3 — .env.
TTS_SERVER=elevenlabs
ELEVENLABS_API_KEY=sk_REPLACE_ME
ELEVENLABS_VOICE_ID=EXAVITQu4vr4xnSDxMaL
ELEVENLABS_MODEL_ID=eleven_turbo_v2_5
ELEVENLABS_STABILITY=0.5
ELEVENLABS_SIMILARITY=0.75
Step 4 — rebuild + restart.
cd ~/whisplay-ai-chatbot
bash build.sh
sudo systemctl restart chatbot.service
Voice IDs: log into elevenlabs.io, open VoiceLab, and copy the ID of any voice you've cloned or one of their stock voices. eleven_turbo_v2_5 is recommended for the Pi Zero 2 WH — it has the lowest latency. Cost is roughly $0.18 per 1000 chars (~7-8 cents per minute of speech).
The
.env.templateevolves. If your file looks different from this guide, the live template at github.com/PiSugar/whisplay-ai-chatbot/blob/master/.env.template is the source of truth.
Save: Ctrl+X, Y, Enter.
8.2 Build the project #
bash build.sh
This compiles the TypeScript and prepares assets. ~5–10 minutes on a Pi Zero 2 WH.
✅ Checkpoint: build.sh exits cleanly with no errors.
8.3 Configure the WonderEcho wake word #
The WonderEcho module runs its own on-device wake-word detector — the Pi doesn't have to listen. You program the trigger phrase ("Claudia") once over I²C, then the module flags a wake event on the bus whenever it hears the word; the Pi polls that register and starts a recording session each time it fires.
Verify before running. The exact I²C register layout (
0x10as the "set-trigger" opcode below) depends on your WonderEcho firmware revision. Check the Hiwonder WonderEcho wiki for the register map matching your unit before running this — the snippet is the canonical pattern, not a guaranteed copy-paste for every shipping firmware.
# Reference snippet: writes the trigger word to the WonderEcho's "set-trigger"
# register. Confirm the register/opcode against the Hiwonder wiki for your
# firmware revision before relying on this in production.
cd ~/whisplay-ai-chatbot
python3 - <<'PY'
import smbus2 as smbus, time
bus = smbus.SMBus(1) # I²C bus 1 on the Pi Zero
ADDR = 0x52 # WonderEcho default — confirm with i2cdetect
WORD = b"claudia"
bus.write_i2c_block_data(ADDR, 0x10, list(WORD) + [0]) # 0x10 = set-trigger
time.sleep(0.2) # let the WonderEcho commit the trigger to its on-board flash before we close the bus
print("Wake word programmed:", WORD.decode())
PY
The chatbot service polls the WonderEcho's wake-event register over I²C and starts a recording session each time the word fires. No Python venv, no openWakeWord, no training.
✅ Checkpoint: speak "Claudia" near the module — journalctl -u chatbot.service -f shows a wake event within ~300 ms.
The exact register map can vary by firmware revision. If your unit reports a different I²C address (verify with
i2cdetect -y 1) or uses a different "set-trigger" opcode, check the WonderEcho wiki for the map matching your firmware.
09. Healthcheck #
Before launching the full chatbot, run a 90-second healthcheck that verifies three layers: the WonderEcho is present on the I²C bus, the network can reach Anthropic, and your API key + chosen model actually return a response. (The audio path itself — speaker out, mic in — is exercised by the manual launch in Part 10.)
Create the script:
nano ~/healthcheck.sh
Paste:
#!/bin/bash
# claudia healthcheck — quick end-to-end smoke test
# Usage: bash ~/healthcheck.sh
set -u
ENV_FILE="$HOME/whisplay-ai-chatbot/.env"
PASS="\033[0;32m✓\033[0m"
FAIL="\033[0;31m✗\033[0m"
exit_code=0
step() { printf "\n%s\n" "── $1 ──"; }
ok() { printf " $PASS %s\n" "$1"; }
bad() { printf " $FAIL %s\n" "$1"; exit_code=1; }
step "1. WonderEcho module on I2C"
# The WonderEcho carries both mic and speaker on-board and talks to the Pi
# over I2C bus 1. We don't expect a standalone ALSA card.
if command -v i2cdetect >/dev/null 2>&1; then
if i2cdetect -y 1 2>/dev/null | grep -qE ' 5[234] '; then
ok "WonderEcho detected on I2C bus 1"
else
bad "WonderEcho NOT detected on I2C bus 1 (check 4-pin wiring + 'sudo raspi-config nonint do_i2c 0')"
fi
else
bad "i2c-tools not installed - run 'sudo apt install -y i2c-tools' (see Part 05.4)"
fi
step "2. Network reachability"
# Use HTTPS instead of ping — many networks/APIs drop ICMP but pass TLS.
# A 4xx response still proves we got a real reply from api.anthropic.com.
net_code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 5 https://api.anthropic.com/ 2>/dev/null || echo "000")
if [ "$net_code" != "000" ]; then
ok "api.anthropic.com responded (HTTP $net_code)"
else
bad "cannot reach api.anthropic.com (Wi-Fi, DNS, or TLS issue)"
fi
step "3. Claude API call"
if [ ! -f "$ENV_FILE" ]; then
bad "$ENV_FILE not found — finish Part 08 first"
else
# shellcheck disable=SC1090
set -a; source "$ENV_FILE"; set +a
if [ -z "${ANTHROPIC_API_KEY:-}" ]; then
bad "ANTHROPIC_API_KEY is empty in .env"
else
response=$(curl -s -w "\n%{http_code}" https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "{\"model\":\"${ANTHROPIC_MODEL:-claude-haiku-4-5-20251001}\",\"max_tokens\":50,\"messages\":[{\"role\":\"user\",\"content\":\"Say hello in exactly 5 words.\"}]}")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" = "200" ]; then
ok "Claude API responded HTTP 200"
# Prefer jq if available — it handles escaped quotes correctly. Fall
# back to a grep+sed that breaks on escapes but is good enough for a
# smoke-test "did Claude reply" sanity check.
if command -v jq >/dev/null 2>&1; then
reply=$(echo "$body" | jq -r '.content[0].text // empty' 2>/dev/null)
else
reply=$(echo "$body" | grep -o '"text":"[^"]*"' | head -1 | sed 's/"text":"//;s/"$//')
fi
echo " Reply: $reply"
else
bad "Claude API returned HTTP $http_code"
echo " $body" | head -3
fi
fi
fi
echo
if [ $exit_code -eq 0 ]; then
printf "$PASS All checks passed. You're ready for Part 10.\n"
else
printf "$FAIL One or more checks failed. Fix above before running the chatbot.\n"
fi
exit $exit_code
Run it:
chmod +x ~/healthcheck.sh
bash ~/healthcheck.sh
✅ Checkpoint: All three sections print green check marks. If anything fails, fix that piece before moving on — running the full chatbot before this passes just makes debugging harder.
10. Run #
Manual launch (foreground, for testing) #
cd ~/whisplay-ai-chatbot
bash run_chatbot.sh
Say "Claudia" — the WonderEcho hears the wake word, the chatbot starts a recording session, you ask your question, and Claude answers out loud. Sessions end automatically after 60 seconds of silence or when you say a stop word (byebye, goodbye, or stop).
Stop the foreground process with Ctrl+C.
Set it to start on boot #
The repo provides an opinionated startup installer that registers a chatbot.service systemd unit and sets the system to multi-user (headless) mode. Use it:
cd ~/whisplay-ai-chatbot
bash startup.sh
After this, the chatbot starts automatically on every boot. Verify:
sudo systemctl status chatbot.service
You should see Active: active (running).
Live logs #
tail -f ~/whisplay-ai-chatbot/chatbot.log
# or
journalctl -u chatbot.service -f
Tuning wake-word reliability #
The WonderEcho exposes a few I²C registers for tuning:
- Too many false wakes (TV, conversations) → raise the detection threshold via the threshold register.
- Missing real wakes (you have to say it twice) → lower the threshold, or move the module closer to where you sit.
Reference: Hiwonder WonderEcho wiki for the exact register map for your firmware revision.
10.5 Smart-home #
You picked a smart plug. Teach Claudia to flip it by giving the chatbot a tool — a small shell command it can invoke when the user's request matches.
TP-Link Kasa (HS103 / KP125M) — local control via python-kasa #
pip install python-kasa --break-system-packages
# Find your plug on the LAN
kasa discover
# Toggle it (replace IP)
kasa --host 192.168.1.42 on
kasa --host 192.168.1.42 off
Wire that into the chatbot by exposing kasa --host <ip> on / off as a tool the LLM can call. No vendor account, no cloud hop — works even if the Kasa cloud is down.
Shelly Plug US — local HTTP #
Find your plug's IP in your router admin or via the Shelly app. Then any HTTP client can flip it:
# On
curl "http://192.168.1.42/relay/0?turn=on"
# Off
curl "http://192.168.1.42/relay/0?turn=off"
No vendor account, no SDK — wire those two curl calls into the chatbot as tools.
Sonoff S31 + Tasmota — local MQTT / HTTP #
Out-of-the-box the S31 uses the eWeLink cloud, which means latency and a dependency on someone else's servers. Reflash with Tasmota (no soldering needed for the S31 — there's a serial header) to expose a local HTTP endpoint:
curl "http://192.168.1.42/cm?cmnd=Power%20On"
curl "http://192.168.1.42/cm?cmnd=Power%20Off"
Slightly more work to flash, but you get full local control + power-usage telemetry over MQTT.
11. Case #
You picked no case. PiSugar publishes free STL files if you change your mind — flip the 3D-printed case config above to FDM or SLA and the right link will appear here.
PiSugar publishes free STL files for case shells:
No printer? Upload the STL to a print service like JLC3DP or Craftcloud — a few dollars shipped.
12. Troubleshooting #
Nothing plays through the speaker #
- The WonderEcho carries the speaker on-board and is driven over I²C — it does not show up in
aplay -l. If you hear nothing, runi2cdetect -y 1and confirm the module's address still answers; if not, the 4-pin cable has come loose. - Check
journalctl -u chatbot.service -ffor "TTS" or "speak" lines — if Claude is replying but the WonderEcho doesn't render, the I²C write path is failing.
Mic captures silence or garbage #
- The mic is on the WonderEcho too — its input is occluded if the module is face-down or covered. Reposition it speaker-side up.
- If the wake event never fires (
journalctl -u chatbot.service -fstays silent when you speak), the wake word may have been reset on cold boot; re-run the I²C programming snippet from Part 08.3.
Build fails out of memory #
- The Pi Zero 2 WH only has 512 MB. Add swap if
build.shgets OOM-killed:sudo dphys-swapfile swapoff sudo sed -i 's/^CONF_SWAPSIZE=.*/CONF_SWAPSIZE=1024/' /etc/dphys-swapfile sudo dphys-swapfile setup sudo dphys-swapfile swapon
Service won't start #
sudo systemctl status chatbot.service --no-pager
journalctl -u chatbot.service -n 60 --no-pager
Look for the first ERROR line — usually a missing .env key or a wrong path.
Claude API returns 401 #
- API key is invalid or expired. Re-copy from console.anthropic.com → API Keys.
Claude API returns 429 #
- You're rate-limited. Add credit at console.anthropic.com → Billing.
WonderEcho doesn't respond #
- Run
i2cdetect -y 1and confirm the module's address still shows up. - Re-run the wake-word programming script in Part 08.3 — flashes can be lost on cold boots.
- Check
journalctl -u chatbot.service -fwhile you speak — if the wake event never fires, the 4-pin I²C cable may have come loose or the module's mic input is occluded.
Wake word triggers on TV / unrelated speech #
- Increase the WonderEcho's detection threshold via I²C — see the Hiwonder wiki for the register address on your firmware revision.
Responses feel slow #
- Use
claude-haiku-4-5-20251001(Part 07 — it's the recommended default for this reason). - The Pi Zero 2 WH's Wi-Fi antenna is weak. Move it closer to the router.
- Local Whisper STT is the slowest step on a Pi Zero 2 WH. If you have a cloud STT key (OpenAI, Google), switching to one of those in
.envcuts perceived latency dramatically.
Need to re-run the healthcheck #
bash ~/healthcheck.sh
SD card filling up #
df -h
sudo apt clean
# clear chatbot recordings:
rm -f ~/whisplay-ai-chatbot/data/recordings/*.wav 2>/dev/null
Reference #
- WonderEcho module: https://www.hiwonder.com/products/wonderecho
- Chatbot repo: https://github.com/PiSugar/whisplay-ai-chatbot
- Claude API docs: https://docs.claude.com
- Claude model catalog: https://docs.claude.com/en/docs/about-claude/models/overview
- Pricing: https://anthropic.com/pricing
Summary stack #
| Layer | What it is |
|---|---|
| Hardware | Pi Zero 2 WH + Hiwonder WonderEcho (I²C) (+ optional PiSugar 3 battery) |
| OS | Raspberry Pi OS 64-bit |
| Wake word | "Claudia" — runs on the WonderEcho, no Pi-side listener |
| Speech → text | Local Whisper-cpp, or cloud STT if configured |
| LLM | Claude API (Anthropic) |
| Text → speech | OpenAI gpt-4o-mini-tts (recommended), Piper (local), or ElevenLabs (with patch) |
| Service manager | systemd (chatbot.service, set up by startup.sh) |
Only Claude (and your chosen TTS, if cloud) runs in the cloud. Everything else can run on-device.
Update Notes #
2026.05.23e #
- Immediate follow-up re-deploy to publish the
2026.05.23dUpdate Notes entry, which was written locally after the original.23ddeploy had already uploaded. The.23dlive bundle was stamped but missing its own notes;.23ecarries both entries to the live site. No prose, layout, or build-script changes — letter-suffix bump only.
2026.05.23d #
- Routine re-deploy. MindAttic.UiUx sync re-spliced OutfitFont (62.6 KB), AtticFont (57.8 KB), and BackHomeM (1.6 KB) into
build-html.jscleanly; all three files (Claudia.md,Claudia.htm,index.htm) uploaded successfully tomindattic.com/claudia/. No prose, layout, or build-script changes — letter-suffix bump only.
2026.05.23c #
- Routine re-deploy with the refreshed MindAttic.UiUx font payloads (OutfitFont, AtticFont, BackHomeM re-spliced from the sibling components repo). No prose, layout, or build-script changes — letter-suffix bump only, covering the intervening
2026.05.23a/2026.05.23bdeploy iterations.
2026.05.22h #
/commitnow also pushes. The project-scopedcommitslash command at.claude/commands/commit.mdpreviously instructed Claude to commit and stop ("Do NOT push"). It now mirrors the global behavior —git pushafter every commit (sets-u origin <branch>if no upstream), with safety rails: no--no-verify, no--force/--force-with-lease, and a hard stop with a user-facing warning if the push is rejected onmain/master. No Claudia content changes.
2026.05.22g #
/deploynow pulls MindAttic.UiUx first.scripts/cli/deploy.ps1gained a sync stage between the version bump and the build: it invokesMindAttic.UiUx/sync/sync-claudia.ps1(looked up as a sibling of the Claudia repo, or overridden via$env:MINDATTIC_COMPONENTS_ROOT) to splice the latest subscribed component CSS (OutfitFont, AtticFont, BackHomeM) intobuild-html.jsbeforenode build-html.jsruns. A new-NoSyncswitch skips it for machines that don't have the Components repo cloned, and a missing sibling folder produces a warning instead of a hard failure. The.claude/commands/deploy.mddoc was updated to describe the new stage.
2026.05.22e #
- TOC absolutely positioned. Added
position: absolute; top: 60pxto.tocinbuild-html.jsso the table-of-contents lifts out of normal sidebar flow. - Wake mechanism: last "interrupt line" mention purged. The 22c sweep unified Parts 08 / 08.3 / 10 around "polls the WonderEcho's wake-event register over I²C", but the matching troubleshooting bullet still said "the I²C interrupt line may be miswired". Now says "the 4-pin I²C cable may have come loose" — consistent with the actual wiring (SDA/SCL/5V/GND, no interrupt GPIO).
- Register-map stability contradiction resolved. Part 08.3 had two adjacent notes that disagreed — one said the layout "depends on your WonderEcho firmware revision", the other said Hiwonder ships a "fixed register map". Both now point at the wiki as the source of truth and acknowledge revision drift.
- Healthcheck intro now matches what the script actually tests. Used to claim "speaker, mic, network, Claude API"; the script never exercises the speaker or mic. Updated to "WonderEcho on I²C, network, Claude API" — and notes the audio path itself is verified by the manual launch in Part 10.
- Network check no longer relies on ICMP. Replaced
ping api.anthropic.com(which silently fails on networks that drop ICMP) with an HTTPS probe viacurl— a 4xx still proves reachability without needing valid auth. - JSON reply extraction prefers
jqwhen available. Thegrep '"text":"…"'pattern breaks on responses containing escaped quotes. The healthcheck now usesjq -r '.content[0].text'if installed, and falls back to the grep/sed pattern for hosts that don't have jq. - Last two "Pi Zero" stragglers normalized. Parts 05.1 and 12 ("This takes 5–15 minutes on a Pi Zero", "Local Whisper STT is the slowest step on a Pi Zero") joined the rest of the body in saying "Pi Zero 2 WH".
- ElevenLabs handler no longer returns NaN duration.
mp3-durationcan returnundefinedon parse failure, andundefined * 1000 = NaN. Coerced with(duration ?? 0) * 1000so the caller always sees a real number. - Redundant
dotenv.config()removed from the ElevenLabs plugin. The chatbot already loads.envat startup; callingdotenv.config()on plugin import was an unconditional side effect that ran whether or not the plugin was activated. - Smart-plug regional note covers all three vendors. The "Before you start" callout used to single out Shelly and Kasa for international plug variants; Sonoff (the third option) has them too. Now framed as "each vendor sells EU/UK/AU variants — pick your region at checkout."
2026.05.22d #
- Routine re-deploy after committing 2026.05.22c's 10 doc fixes. No source changes — letter-suffix bump only.
2026.05.22c #
- Build-pipeline placeholder leak fixed. Part 06's Node-version checkpoint was rendering the literal
{{NODE_LABEL}}template token instead of a real version. Replaced withv20.x(current Node LTS that upstream's installer pulls). - Default Pi OS username removed. Part 04 used to recommend
Username: pi, but Bookworm-era Imager warns or refuses that name. The guide now tells you to pick anything else (e.g.claudiaor your first name) and downstream SSH commands + the checkpoint prompt use<your-username>@claudiainstead ofpi@claudia. - Hardcoded timezone unmasked.
America/Chicagosat next to genuine placeholders in the Imager settings list, so international readers were copying a Central-US timezone verbatim. Now framed as "your timezone (e.g.America/Chicago)". - Wake-trigger mechanism unified. Parts 08 / 08.3 / 10 previously described the WonderEcho's wake signal three different ways ("wake-up signal over I²C" / "pulls a GPIO" / "watches the interrupt line"). All three now say the Pi polls a wake-event register over I²C — matches the 4-pin SDA/SCL/5V/GND wiring (no dedicated interrupt GPIO).
- i2cdetect regex tightened. Part 09 healthcheck's
grep -qE '52|53|54'matched those substrings anywhere on a line. Anchored to a matrix cell (' 5[234] ') so column-header lines can't false-positive. - SD-reader callout moved before the Imager download line in Part 04.1, so readers see "you might need a USB microSD reader" before they leave to download Imager rather than after.
- Cross-reference numbering normalized. Section headings use two-digit prefixes (
## 04.,## 05., …), so cross-refs in the healthcheck script and troubleshooting section were standardized to match: "Part 5.4" → "Part 05.4", "Part 8" → "Part 08", "Part 8.3" → "Part 08.3", "Part 7" → "Part 07". - Pi Zero 2 W → WH nomenclature aligned. After the recent power-supply-card fix established WH as canonical, eight remaining body mentions of "Pi Zero 2 W" (RAM checkpoint, dependency-install time, ASR/TTS latency notes, OOM troubleshooting, Wi-Fi note) were converted. The Imager UI step keeps
Raspberry Pi Zero 2 Wsince that's literally what Imager shows. - WonderEcho I²C snippet labeled as a reference, not a guaranteed paste. Part 08.3's
bus.write_i2c_block_data(ADDR, 0x10, …)script now sits under a "verify before running" callout pointing at the Hiwonder wiki for the register map of the reader's specific firmware revision. time.sleep(0.2)annotated. The unexplained 200 ms delay after writing the trigger word now has an inline comment explaining it lets the WonderEcho commit the trigger to its on-board flash before the bus closes.
2026.05.22b #
- Outfit variable font now embedded. The build pipeline (via
MindAttic.UiUx/sync/sync-claudia.ps1) now inlines the Outfit variable font (weights 100–900) directly intoClaudia.htmas a base64data:URL, so the live page no longer depends on Google Fonts and renders identically offline. No content changes — same words, same layout, just the typography source.
2026.05.20k #
- "Before you start" callout. Added explicit prerequisites near the top — host computer, microSD reader caveat (the SanDisk Ultra ships with a full-size SD adapter only — no USB reader; most modern laptops lack any SD slot), 2.4 GHz Wi-Fi requirement (the Pi Zero 2 W has no 5 GHz radio), regional note for the smart-plug options, and a stock-check pointer to rpilocator.com for the Pi Zero 2 WH.
- WonderEcho cable confirmation. Hiwonder's packing list confirms the 4-pin Dupont cable ships in the WonderEcho box — added that fact to the part card spec table, the assembly step, and the prerequisites callout so builders don't go hunting for jumper wires.
- Store-link refresh. Replaced three broken or stale "Official" URLs in the parts catalog: TP-Link Kasa HS103 (the old slug now 500s, swapped to
kasa-smart-wifi-mini-plug-hs103), Shelly Plug US (the old URL 404s — moved to the current Gen 4 product atus.shelly.com), and the Pi Hut power-supply page (redirected to its homepage — swapped to the power-supply collection page so it survives future SKU rotation). - Prices refreshed (2026-05-22): Pi Zero 2 WH $22 → $20, PSU $10 → $9, WonderEcho $25 → $24, Sonoff S31 $13 → $10. Desktop total now ~$62, portable ~$102.
2026.05.20a #
- Pi Zero 2 WH callout. Added a top-of-guide note clarifying the build needs the WH (with pre-soldered headers) variant — the plain "W" has no GPIO pins and would require 40 pins soldered before the WonderEcho's 4-pin cable can connect. The parts-catalog buy links now point at verified WH product pages (Sparkfun direct WH page; The Pi Hut and CanaKit pages that carry the WH variant) and the part card carries an inline reminder to pick "with headers" on retailers that list it as a dropdown variant. Dropped the Adafruit link since they don't currently stock the Pi Zero 2 WH.
- Healthcheck pivoted to I²C. The audio-card checks in Part 09 (
aplay -l | grep wm8960,arecordsmoke test) were stale —wm8960was the dropped Whisplay HAT chip, and the WonderEcho carries its mic and speaker on-board over I²C rather than appearing as an ALSA card. Replaced with ani2cdetectcheck for the module's I²C address. - Troubleshooting refresh. "Nothing plays through the speaker" / "Mic captures silence" now point at the WonderEcho's I²C plumbing instead of the dropped
alsamixer/wm8960flow. - Cross-reference fix. "Re-run the wake-word programming script in 9.3" → Part 8.3.
2026.05.19f #
- Sections renumbered to start at 01.
02. Configure→01. Configure, with every subsequent section shifted down by one (and sub-headings + cross-references re-targeted to match). <pre>overflow-x removed, so long code lines no longer trigger horizontal scroll inside their box.- Flex-shrink bug fixed. The sticky-footer layout was squashing
<pre>blocks and tables to a single line when total content exceeded the viewport; direct children ofmain.pagenow useflex-shrink: 0so they keep their natural height. - Stray USB-mic troubleshooting line (referencing the deleted Part 4.5) cleaned up.
2026.05.19e #
- Hidden all standalone
<hr>rules — the H2 top-margin already provides the section gap, and the rules read as visual noise on a long page.
2026.05.19d #
- Dropped the "Different architecture…" note from the WonderEcho card now that the module is core to the build instead of a niche alternative.
2026.05.19c #
- Routine re-deploy. No source changes — letter-suffix bump only.
2026.05.19b #
- Build versioning. Stamp is now
YYYY.MM.DD<letter>and auto-bumps on each deploy: same day → next letter (a→b→c…), new day → reset toa. The Update Notes headings adopt the same format with no description after the date. - Footer rework. Replaced the "Built for MindAttic LLC — date" line and the old "Generated by Claudia build-html.js…" rendered footer with a single
© <auto-year> MindAttic LLCline. The new footer pins to the bottom of the main pane when content is short (margin-top: autoon a flex column) and flows naturally at the end of the page otherwise. bump-version.ps1simplified: now only rewrites the*Last updated:*line (the date no longer lives in the H1 or the footer).
2026.05.19a #
- Hardware change. Dropped the PiSugar Whisplay HAT, SunFounder mic, reSpeaker XVF3800, and the OTG adapter. The build is now Pi Zero 2 W + Hiwonder WonderEcho (I²C) + power + microSD. The WonderEcho provides the mic, speaker, and wake-word detection in one module.
- Wake word. No more openWakeWord, no more Python 3.11 pyenv, no more custom-model training. The WonderEcho runs the keyword detector in its own firmware; you program "Claudia" once over I²C (section 08.3).
- TTS defaults. OpenAI
gpt-4o-mini-ttsis the new recommended TTS — natively supported by upstream. Piper still available for local. ElevenLabs added as a patchable option (drop-in TypeScript handler documented in section 08 — TTS subsection). - Env-key fix. Corrected
*_PROVIDER→*_SERVEReverywhere (LLM_SERVER,ASR_SERVER,TTS_SERVER) to match upstream's plugin registry. - Interactive guide. Configure-your-build widget at the top adapts the steps below to your picks; selections persist in
localStorage.
2026.05.18a #
- One path, not two. The PiSugar
whisplay-ai-chatbotrepo is purpose-built for this exact hardware and is the right choice for a Pi Zero 2 W. - Verified install commands against the live upstream repos.
- Checkpoint tests at the end of each Part.
- First-run
healthcheck.shscript (Part 09). - systemd unit dropped — the repo's
startup.shsets upchatbot.serviceproperly. - Mic decision moved to the front as a 3-question flowchart.
- Cost tiers cut from 4 to 2. Desktop or portable.