feat: Raspberry Pi 5 kitchen satellite — Wyoming voice satellite with ReSpeaker pHAT

Add full Pi 5 satellite setup with ReSpeaker 2-Mics pHAT for kitchen
voice control via Wyoming protocol. Includes satellite_wrapper.py that
monkey-patches WakeStreamingSatellite to fix three compounding bugs:

- TTS echo suppression: mutes wake word detection while speaker plays
- Server writer race fix: checks _writer before streaming, re-arms on None
- Streaming timeout: auto-recovers after 30s if pipeline hangs
- Error recovery: resets streaming state on server Error events

Also includes Pi 5 hardware workarounds (wm8960 overlay, stereo-only
audio wrappers, ALSA mixer calibration) and deploy.sh with fast
iteration commands (--push-wrapper, --test-logs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Aodhan Collins
2026-03-14 20:09:47 +00:00
parent 5f147cae61
commit 1e52c002c2
7 changed files with 1024 additions and 79 deletions

159
homeai-rpi/deploy.sh Executable file
View File

@@ -0,0 +1,159 @@
#!/usr/bin/env bash
# homeai-rpi/deploy.sh — Deploy/manage Wyoming Satellite on Raspberry Pi from Mac Mini
#
# Usage:
# ./deploy.sh — full setup (push + install on Pi)
# ./deploy.sh --status — check satellite status
# ./deploy.sh --restart — restart satellite service
# ./deploy.sh --logs — tail satellite logs
# ./deploy.sh --test-audio — record 3s from mic, play back through speaker
# ./deploy.sh --update — update Python packages only
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ─── Pi connection ──────────────────────────────────────────────────────────
PI_HOST="SELBINA.local"
PI_USER="aodhan"
PI_SSH="${PI_USER}@${PI_HOST}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
log_step() { echo -e "${CYAN}[STEP]${NC} $*"; }
# ─── SSH helpers ────────────────────────────────────────────────────────────
pi_ssh() {
ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new "${PI_SSH}" "$@"
}
pi_scp() {
scp -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new "$@"
}
check_connectivity() {
log_step "Checking connectivity to ${PI_HOST}..."
if ! ping -c 1 -t 3 "${PI_HOST}" &>/dev/null; then
log_error "Cannot reach ${PI_HOST}. Is the Pi on?"
fi
if ! pi_ssh "echo ok" &>/dev/null; then
log_error "SSH to ${PI_SSH} failed. Set up SSH keys:
ssh-copy-id ${PI_SSH}"
fi
log_ok "Connected to ${PI_HOST}"
}
# ─── Commands ───────────────────────────────────────────────────────────────
cmd_setup() {
check_connectivity
log_step "Pushing setup script to Pi..."
pi_scp "${SCRIPT_DIR}/setup.sh" "${PI_SSH}:~/homeai-satellite-setup.sh"
log_step "Running setup on Pi..."
pi_ssh "chmod +x ~/homeai-satellite-setup.sh && ~/homeai-satellite-setup.sh"
log_ok "Setup complete!"
}
cmd_status() {
check_connectivity
log_step "Satellite status:"
pi_ssh "systemctl status homeai-satellite.service --no-pager" || true
}
cmd_restart() {
check_connectivity
log_step "Restarting satellite..."
pi_ssh "sudo systemctl restart homeai-satellite.service"
sleep 2
pi_ssh "systemctl is-active homeai-satellite.service" && log_ok "Satellite running" || log_warn "Satellite not active"
}
cmd_logs() {
check_connectivity
log_info "Tailing satellite logs (Ctrl+C to stop)..."
pi_ssh "journalctl -u homeai-satellite.service -f --no-hostname"
}
cmd_test_audio() {
check_connectivity
log_step "Recording 3 seconds from mic..."
pi_ssh "arecord -D plughw:2,0 -d 3 -f S16_LE -r 16000 -c 1 /tmp/homeai-test.wav 2>/dev/null"
log_step "Playing back through speaker..."
pi_ssh "aplay -D plughw:2,0 /tmp/homeai-test.wav 2>/dev/null"
log_ok "Audio test complete. Did you hear yourself?"
}
cmd_update() {
check_connectivity
log_step "Updating Python packages on Pi..."
pi_ssh "source ~/homeai-satellite/venv/bin/activate && pip install --upgrade wyoming-satellite openwakeword -q"
log_step "Pushing latest scripts..."
pi_scp "${SCRIPT_DIR}/satellite_wrapper.py" "${PI_SSH}:~/homeai-satellite/satellite_wrapper.py"
pi_ssh "sudo systemctl restart homeai-satellite.service"
log_ok "Updated and restarted"
}
cmd_push_wrapper() {
check_connectivity
log_step "Pushing satellite_wrapper.py..."
pi_scp "${SCRIPT_DIR}/satellite_wrapper.py" "${PI_SSH}:~/homeai-satellite/satellite_wrapper.py"
log_step "Restarting satellite..."
pi_ssh "sudo systemctl restart homeai-satellite.service"
sleep 2
pi_ssh "systemctl is-active homeai-satellite.service" && log_ok "Satellite running" || log_warn "Satellite not active — check logs"
}
cmd_test_logs() {
check_connectivity
log_info "Filtered satellite logs — key events only (Ctrl+C to stop)..."
pi_ssh "journalctl -u homeai-satellite.service -f --no-hostname" \
| grep --line-buffered -iE \
'Waiting for wake|Streaming audio|transcript|synthesize|Speaker active|unmute|_writer|timeout|error|Error|Wake word detected|re-arming|resetting'
}
# ─── Main ───────────────────────────────────────────────────────────────────
case "${1:-}" in
--status) cmd_status ;;
--restart) cmd_restart ;;
--logs) cmd_logs ;;
--test-audio) cmd_test_audio ;;
--test-logs) cmd_test_logs ;;
--update) cmd_update ;;
--push-wrapper) cmd_push_wrapper ;;
--help|-h)
echo "Usage: $0 [command]"
echo ""
echo "Commands:"
echo " (none) Full setup — push and install satellite on Pi"
echo " --status Check satellite service status"
echo " --restart Restart satellite service"
echo " --logs Tail satellite logs (live, all)"
echo " --test-logs Tail filtered logs (key events only)"
echo " --test-audio Record 3s from mic, play back on speaker"
echo " --push-wrapper Push satellite_wrapper.py and restart (fast iteration)"
echo " --update Update packages and restart"
echo " --help Show this help"
echo ""
echo "Pi: ${PI_SSH} (${PI_HOST})"
;;
"") cmd_setup ;;
*) log_error "Unknown command: $1. Use --help for usage." ;;
esac