Ports prior voice assistant research and prototypes from devl/Devops into the Minerva repo. Includes: - docs/: architecture, wake word guides, ESP32-S3 spec, hardware buying guide - scripts/: voice_server.py, voice_server_enhanced.py, setup scripts - hardware/maixduino/: edge device scripts with WiFi credentials scrubbed (replaced hardcoded password with secrets.py pattern) - config/.env.example: server config template - .gitignore: excludes .env, secrets.py, model blobs, ELF firmware - CLAUDE.md: Minerva product context and connection to cf-voice roadmap
429 lines
13 KiB
Bash
Executable file
429 lines
13 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Path: setup_voice_assistant.sh
|
|
#
|
|
# Purpose and usage:
|
|
# Sets up the voice assistant server environment on Heimdall
|
|
# - Creates conda environment
|
|
# - Installs dependencies (Whisper, Flask, Piper TTS)
|
|
# - Downloads and configures TTS models
|
|
# - Sets up systemd service (optional)
|
|
# - Configures environment variables
|
|
#
|
|
# Requirements:
|
|
# - conda/miniconda installed
|
|
# - Internet connection for downloads
|
|
# - Sudo access (for systemd service setup)
|
|
#
|
|
# Usage:
|
|
# ./setup_voice_assistant.sh [--no-service] [--env-name NAME]
|
|
#
|
|
# Author: PRbL Library
|
|
# Created: $(date +"%Y-%m-%d")
|
|
|
|
# ----- PRbL Color and output functions -----
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
PURPLE='\033[0;35m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m' # No Color
|
|
|
|
print_status() {
|
|
local level="$1"
|
|
shift
|
|
case "$level" in
|
|
"info") echo -e "${BLUE}[INFO]${NC} $*" >&2 ;;
|
|
"success") echo -e "${GREEN}[SUCCESS]${NC} $*" >&2 ;;
|
|
"warning") echo -e "${YELLOW}[WARNING]${NC} $*" >&2 ;;
|
|
"error") echo -e "${RED}[ERROR]${NC} $*" >&2 ;;
|
|
"debug") [[ "$VERBOSE" == "true" ]] && echo -e "${PURPLE}[DEBUG]${NC} $*" >&2 ;;
|
|
*) echo -e "$*" >&2 ;;
|
|
esac
|
|
}
|
|
|
|
# ----- Configuration -----
|
|
CONDA_ENV_NAME="voice-assistant"
|
|
PROJECT_DIR="$HOME/voice-assistant"
|
|
INSTALL_SYSTEMD=true
|
|
VERBOSE=false
|
|
|
|
# ----- Dependency checking -----
|
|
command_exists() {
|
|
command -v "$1" &> /dev/null
|
|
}
|
|
|
|
check_conda() {
|
|
if ! command_exists conda; then
|
|
print_status error "conda not found. Please install miniconda first."
|
|
print_status info "Install with: wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh"
|
|
print_status info " bash Miniconda3-latest-Linux-x86_64.sh"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# ----- Parse arguments -----
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--no-service)
|
|
INSTALL_SYSTEMD=false
|
|
shift
|
|
;;
|
|
--env-name)
|
|
CONDA_ENV_NAME="$2"
|
|
shift 2
|
|
;;
|
|
-v|--verbose)
|
|
VERBOSE=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
cat << EOF
|
|
Usage: $(basename "$0") [OPTIONS]
|
|
|
|
Options:
|
|
--no-service Don't install systemd service
|
|
--env-name NAME Custom conda environment name (default: voice-assistant)
|
|
-v, --verbose Enable verbose output
|
|
-h, --help Show this help message
|
|
|
|
EOF
|
|
exit 0
|
|
;;
|
|
*)
|
|
print_status error "Unknown option: $1"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# ----- Setup functions -----
|
|
|
|
create_project_directory() {
|
|
print_status info "Creating project directory: $PROJECT_DIR"
|
|
|
|
if [[ ! -d "$PROJECT_DIR" ]]; then
|
|
mkdir -p "$PROJECT_DIR" || {
|
|
print_status error "Failed to create project directory"
|
|
return 1
|
|
}
|
|
fi
|
|
|
|
# Create subdirectories
|
|
mkdir -p "$PROJECT_DIR"/{logs,models,config}
|
|
|
|
print_status success "Project directory created"
|
|
return 0
|
|
}
|
|
|
|
create_conda_environment() {
|
|
print_status info "Creating conda environment: $CONDA_ENV_NAME"
|
|
|
|
# Check if environment already exists
|
|
if conda env list | grep -q "^${CONDA_ENV_NAME}\s"; then
|
|
print_status warning "Environment $CONDA_ENV_NAME already exists"
|
|
read -p "Remove and recreate? (y/N): " -n 1 -r
|
|
echo
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
print_status info "Removing existing environment..."
|
|
conda env remove -n "$CONDA_ENV_NAME" -y
|
|
else
|
|
print_status info "Using existing environment"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Create new environment
|
|
print_status info "Creating Python 3.10 environment..."
|
|
conda create -n "$CONDA_ENV_NAME" python=3.10 -y || {
|
|
print_status error "Failed to create conda environment"
|
|
return 1
|
|
}
|
|
|
|
print_status success "Conda environment created"
|
|
return 0
|
|
}
|
|
|
|
install_python_dependencies() {
|
|
print_status info "Installing Python dependencies..."
|
|
|
|
# Activate conda environment
|
|
eval "$(conda shell.bash hook)"
|
|
conda activate "$CONDA_ENV_NAME" || {
|
|
print_status error "Failed to activate conda environment"
|
|
return 1
|
|
}
|
|
|
|
# Install base dependencies
|
|
print_status info "Installing base packages..."
|
|
pip install --upgrade pip --break-system-packages || true
|
|
|
|
# Install Whisper (OpenAI)
|
|
print_status info "Installing OpenAI Whisper..."
|
|
pip install -U openai-whisper --break-system-packages || {
|
|
print_status error "Failed to install Whisper"
|
|
return 1
|
|
}
|
|
|
|
# Install Flask
|
|
print_status info "Installing Flask..."
|
|
pip install flask --break-system-packages || {
|
|
print_status error "Failed to install Flask"
|
|
return 1
|
|
}
|
|
|
|
# Install requests
|
|
print_status info "Installing requests..."
|
|
pip install requests --break-system-packages || {
|
|
print_status error "Failed to install requests"
|
|
return 1
|
|
}
|
|
|
|
# Install python-dotenv
|
|
print_status info "Installing python-dotenv..."
|
|
pip install python-dotenv --break-system-packages || {
|
|
print_status warning "Failed to install python-dotenv (optional)"
|
|
}
|
|
|
|
# Install Piper TTS
|
|
print_status info "Installing Piper TTS..."
|
|
# Note: Piper TTS installation method varies, adjust as needed
|
|
# For now, we'll install the Python package if available
|
|
pip install piper-tts --break-system-packages || {
|
|
print_status warning "Piper TTS pip package not found"
|
|
print_status info "You may need to install Piper manually from: https://github.com/rhasspy/piper"
|
|
}
|
|
|
|
# Install PyAudio for audio handling
|
|
print_status info "Installing PyAudio dependencies..."
|
|
if command_exists apt-get; then
|
|
sudo apt-get install -y portaudio19-dev python3-pyaudio || {
|
|
print_status warning "Failed to install portaudio dev packages"
|
|
}
|
|
fi
|
|
|
|
pip install pyaudio --break-system-packages || {
|
|
print_status warning "Failed to install PyAudio (may need manual installation)"
|
|
}
|
|
|
|
print_status success "Python dependencies installed"
|
|
return 0
|
|
}
|
|
|
|
download_piper_models() {
|
|
print_status info "Downloading Piper TTS models..."
|
|
|
|
local models_dir="$PROJECT_DIR/models/piper"
|
|
mkdir -p "$models_dir"
|
|
|
|
# Download a default voice model
|
|
# Example: en_US-lessac-medium
|
|
local model_url="https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx"
|
|
local config_url="https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx.json"
|
|
|
|
if [[ ! -f "$models_dir/en_US-lessac-medium.onnx" ]]; then
|
|
print_status info "Downloading voice model..."
|
|
wget -q --show-progress -O "$models_dir/en_US-lessac-medium.onnx" "$model_url" || {
|
|
print_status warning "Failed to download Piper model (manual download may be needed)"
|
|
}
|
|
|
|
wget -q --show-progress -O "$models_dir/en_US-lessac-medium.onnx.json" "$config_url" || {
|
|
print_status warning "Failed to download Piper config"
|
|
}
|
|
else
|
|
print_status info "Piper model already downloaded"
|
|
fi
|
|
|
|
print_status success "Piper models ready"
|
|
return 0
|
|
}
|
|
|
|
create_config_file() {
|
|
print_status info "Creating configuration file..."
|
|
|
|
local config_file="$PROJECT_DIR/config/.env"
|
|
|
|
if [[ -f "$config_file" ]]; then
|
|
print_status warning "Config file already exists: $config_file"
|
|
return 0
|
|
fi
|
|
|
|
cat > "$config_file" << 'EOF'
|
|
# Voice Assistant Configuration
|
|
# Path: ~/voice-assistant/config/.env
|
|
|
|
# Home Assistant Configuration
|
|
HA_URL=http://homeassistant.local:8123
|
|
HA_TOKEN=your_long_lived_access_token_here
|
|
|
|
# Server Configuration
|
|
SERVER_HOST=0.0.0.0
|
|
SERVER_PORT=5000
|
|
|
|
# Whisper Configuration
|
|
WHISPER_MODEL=medium
|
|
|
|
# Piper TTS Configuration
|
|
PIPER_MODEL=/path/to/piper/model.onnx
|
|
PIPER_CONFIG=/path/to/piper/model.onnx.json
|
|
|
|
# Logging
|
|
LOG_LEVEL=INFO
|
|
LOG_FILE=/home/$USER/voice-assistant/logs/voice_assistant.log
|
|
EOF
|
|
|
|
# Update paths in config
|
|
sed -i "s|/path/to/piper/model.onnx|$PROJECT_DIR/models/piper/en_US-lessac-medium.onnx|g" "$config_file"
|
|
sed -i "s|/path/to/piper/model.onnx.json|$PROJECT_DIR/models/piper/en_US-lessac-medium.onnx.json|g" "$config_file"
|
|
sed -i "s|/home/\$USER|$HOME|g" "$config_file"
|
|
|
|
chmod 600 "$config_file"
|
|
|
|
print_status success "Config file created: $config_file"
|
|
print_status warning "Please edit $config_file and add your Home Assistant token"
|
|
|
|
return 0
|
|
}
|
|
|
|
create_systemd_service() {
|
|
if [[ "$INSTALL_SYSTEMD" != "true" ]]; then
|
|
print_status info "Skipping systemd service installation"
|
|
return 0
|
|
fi
|
|
|
|
print_status info "Creating systemd service..."
|
|
|
|
local service_file="/etc/systemd/system/voice-assistant.service"
|
|
|
|
# Create service file
|
|
sudo tee "$service_file" > /dev/null << EOF
|
|
[Unit]
|
|
Description=Voice Assistant Server
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=$USER
|
|
WorkingDirectory=$PROJECT_DIR
|
|
Environment="PATH=$HOME/miniconda3/envs/$CONDA_ENV_NAME/bin:/usr/local/bin:/usr/bin:/bin"
|
|
EnvironmentFile=$PROJECT_DIR/config/.env
|
|
ExecStart=$HOME/miniconda3/envs/$CONDA_ENV_NAME/bin/python $PROJECT_DIR/voice_server.py
|
|
Restart=on-failure
|
|
RestartSec=10
|
|
StandardOutput=append:$PROJECT_DIR/logs/voice_assistant.log
|
|
StandardError=append:$PROJECT_DIR/logs/voice_assistant_error.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
# Reload systemd
|
|
sudo systemctl daemon-reload
|
|
|
|
print_status success "Systemd service created"
|
|
print_status info "To enable and start the service:"
|
|
print_status info " sudo systemctl enable voice-assistant"
|
|
print_status info " sudo systemctl start voice-assistant"
|
|
|
|
return 0
|
|
}
|
|
|
|
create_test_script() {
|
|
print_status info "Creating test script..."
|
|
|
|
local test_script="$PROJECT_DIR/test_server.sh"
|
|
|
|
cat > "$test_script" << 'EOF'
|
|
#!/bin/bash
|
|
# Test script for voice assistant server
|
|
|
|
# Activate conda environment
|
|
eval "$(conda shell.bash hook)"
|
|
conda activate voice-assistant
|
|
|
|
# Load environment variables
|
|
if [[ -f ~/voice-assistant/config/.env ]]; then
|
|
export $(grep -v '^#' ~/voice-assistant/config/.env | xargs)
|
|
fi
|
|
|
|
# Run server
|
|
cd ~/voice-assistant
|
|
python voice_server.py --verbose
|
|
EOF
|
|
|
|
chmod +x "$test_script"
|
|
|
|
print_status success "Test script created: $test_script"
|
|
return 0
|
|
}
|
|
|
|
install_voice_server_script() {
|
|
print_status info "Installing voice_server.py..."
|
|
|
|
# Check if voice_server.py exists in outputs
|
|
if [[ -f "$HOME/voice_server.py" ]]; then
|
|
cp "$HOME/voice_server.py" "$PROJECT_DIR/voice_server.py"
|
|
print_status success "voice_server.py installed"
|
|
elif [[ -f "./voice_server.py" ]]; then
|
|
cp "./voice_server.py" "$PROJECT_DIR/voice_server.py"
|
|
print_status success "voice_server.py installed"
|
|
else
|
|
print_status warning "voice_server.py not found in current directory"
|
|
print_status info "Please copy voice_server.py to $PROJECT_DIR manually"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# ----- Main -----
|
|
main() {
|
|
print_status info "Starting voice assistant setup..."
|
|
|
|
# Parse arguments
|
|
parse_args "$@"
|
|
|
|
# Check dependencies
|
|
check_conda || exit 1
|
|
|
|
# Setup steps
|
|
create_project_directory || exit 1
|
|
create_conda_environment || exit 1
|
|
install_python_dependencies || exit 1
|
|
download_piper_models || exit 1
|
|
create_config_file || exit 1
|
|
install_voice_server_script || exit 1
|
|
create_test_script || exit 1
|
|
|
|
if [[ "$INSTALL_SYSTEMD" == "true" ]]; then
|
|
create_systemd_service || exit 1
|
|
fi
|
|
|
|
# Final instructions
|
|
print_status success "Setup complete!"
|
|
echo
|
|
print_status info "Next steps:"
|
|
print_status info "1. Edit config file: vim $PROJECT_DIR/config/.env"
|
|
print_status info "2. Add your Home Assistant long-lived access token"
|
|
print_status info "3. Test the server: $PROJECT_DIR/test_server.sh"
|
|
print_status info "4. Configure your Maix Duino device"
|
|
|
|
if [[ "$INSTALL_SYSTEMD" == "true" ]]; then
|
|
echo
|
|
print_status info "To run as a service:"
|
|
print_status info " sudo systemctl enable voice-assistant"
|
|
print_status info " sudo systemctl start voice-assistant"
|
|
print_status info " sudo systemctl status voice-assistant"
|
|
fi
|
|
|
|
echo
|
|
print_status info "Project directory: $PROJECT_DIR"
|
|
print_status info "Conda environment: $CONDA_ENV_NAME"
|
|
print_status info "Activate with: conda activate $CONDA_ENV_NAME"
|
|
}
|
|
|
|
# Run main
|
|
main "$@"
|