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
630 lines
16 KiB
Bash
Executable file
630 lines
16 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Path: setup_precise.sh
|
|
#
|
|
# Purpose and usage:
|
|
# Sets up Mycroft Precise wake word detection on Heimdall
|
|
# - Creates conda environment for Precise
|
|
# - Installs TensorFlow 1.x and dependencies
|
|
# - Downloads precise-engine
|
|
# - Sets up training directories
|
|
# - Provides helper scripts for training
|
|
#
|
|
# Requirements:
|
|
# - conda/miniconda installed
|
|
# - Internet connection for downloads
|
|
# - Microphone for recording samples
|
|
#
|
|
# Usage:
|
|
# ./setup_precise.sh [--wake-word "phrase"] [--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="precise"
|
|
WAKE_WORD="hey computer"
|
|
MODELS_DIR="$HOME/precise-models"
|
|
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."
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# ----- Parse arguments -----
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--wake-word)
|
|
WAKE_WORD="$2"
|
|
shift 2
|
|
;;
|
|
--env-name)
|
|
CONDA_ENV_NAME="$2"
|
|
shift 2
|
|
;;
|
|
-v|--verbose)
|
|
VERBOSE=true
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
cat << EOF
|
|
Usage: $(basename "$0") [OPTIONS]
|
|
|
|
Options:
|
|
--wake-word "phrase" Wake word to train (default: "hey computer")
|
|
--env-name NAME Custom conda environment name (default: precise)
|
|
-v, --verbose Enable verbose output
|
|
-h, --help Show this help message
|
|
|
|
Examples:
|
|
$(basename "$0") --wake-word "hey jarvis"
|
|
$(basename "$0") --env-name mycroft-precise
|
|
|
|
EOF
|
|
exit 0
|
|
;;
|
|
*)
|
|
print_status error "Unknown option: $1"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# ----- Setup functions -----
|
|
|
|
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 with Python 3.7 (required for TF 1.15)
|
|
print_status info "Creating Python 3.7 environment..."
|
|
conda create -n "$CONDA_ENV_NAME" python=3.7 -y || {
|
|
print_status error "Failed to create conda environment"
|
|
return 1
|
|
}
|
|
|
|
print_status success "Conda environment created"
|
|
return 0
|
|
}
|
|
|
|
install_tensorflow() {
|
|
print_status info "Installing TensorFlow 1.15..."
|
|
|
|
# Activate conda environment
|
|
eval "$(conda shell.bash hook)"
|
|
conda activate "$CONDA_ENV_NAME" || {
|
|
print_status error "Failed to activate conda environment"
|
|
return 1
|
|
}
|
|
|
|
# Install TensorFlow 1.15 (last 1.x version)
|
|
pip install tensorflow==1.15.5 --break-system-packages || {
|
|
print_status error "Failed to install TensorFlow"
|
|
return 1
|
|
}
|
|
|
|
# Verify installation
|
|
python -c "import tensorflow as tf; print(f'TensorFlow {tf.__version__} installed')" || {
|
|
print_status error "TensorFlow installation verification failed"
|
|
return 1
|
|
}
|
|
|
|
print_status success "TensorFlow 1.15 installed"
|
|
return 0
|
|
}
|
|
|
|
install_precise() {
|
|
print_status info "Installing Mycroft Precise..."
|
|
|
|
# Activate conda environment
|
|
eval "$(conda shell.bash hook)"
|
|
conda activate "$CONDA_ENV_NAME" || {
|
|
print_status error "Failed to activate conda environment"
|
|
return 1
|
|
}
|
|
|
|
# Install audio dependencies
|
|
print_status info "Installing system audio dependencies..."
|
|
if command_exists apt-get; then
|
|
sudo apt-get update
|
|
sudo apt-get install -y portaudio19-dev sox libatlas-base-dev || {
|
|
print_status warning "Some audio dependencies failed to install"
|
|
}
|
|
fi
|
|
|
|
# Install Python audio libraries
|
|
pip install pyaudio --break-system-packages || {
|
|
print_status warning "PyAudio installation failed (may need manual installation)"
|
|
}
|
|
|
|
# Install Precise
|
|
pip install mycroft-precise --break-system-packages || {
|
|
print_status error "Failed to install Mycroft Precise"
|
|
return 1
|
|
}
|
|
|
|
# Verify installation
|
|
python -c "import precise_runner; print('Precise installed successfully')" || {
|
|
print_status error "Precise installation verification failed"
|
|
return 1
|
|
}
|
|
|
|
print_status success "Mycroft Precise installed"
|
|
return 0
|
|
}
|
|
|
|
download_precise_engine() {
|
|
print_status info "Downloading precise-engine..."
|
|
|
|
local engine_version="0.3.0"
|
|
local engine_url="https://github.com/MycroftAI/mycroft-precise/releases/download/v${engine_version}/precise-engine_${engine_version}_x86_64.tar.gz"
|
|
local temp_dir=$(mktemp -d)
|
|
|
|
# Download engine
|
|
wget -q --show-progress -O "$temp_dir/precise-engine.tar.gz" "$engine_url" || {
|
|
print_status error "Failed to download precise-engine"
|
|
rm -rf "$temp_dir"
|
|
return 1
|
|
}
|
|
|
|
# Extract
|
|
tar xzf "$temp_dir/precise-engine.tar.gz" -C "$temp_dir" || {
|
|
print_status error "Failed to extract precise-engine"
|
|
rm -rf "$temp_dir"
|
|
return 1
|
|
}
|
|
|
|
# Install to /usr/local/bin
|
|
sudo cp "$temp_dir/precise-engine/precise-engine" /usr/local/bin/ || {
|
|
print_status error "Failed to install precise-engine"
|
|
rm -rf "$temp_dir"
|
|
return 1
|
|
}
|
|
|
|
sudo chmod +x /usr/local/bin/precise-engine
|
|
|
|
# Clean up
|
|
rm -rf "$temp_dir"
|
|
|
|
# Verify installation
|
|
precise-engine --version || {
|
|
print_status error "precise-engine installation verification failed"
|
|
return 1
|
|
}
|
|
|
|
print_status success "precise-engine installed"
|
|
return 0
|
|
}
|
|
|
|
create_training_directory() {
|
|
print_status info "Creating training directory structure..."
|
|
|
|
# Sanitize wake word for directory name
|
|
local wake_word_dir=$(echo "$WAKE_WORD" | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
|
|
local project_dir="$MODELS_DIR/$wake_word_dir"
|
|
|
|
mkdir -p "$project_dir"/{wake-word,not-wake-word,test/wake-word,test/not-wake-word}
|
|
|
|
print_status success "Training directory created: $project_dir"
|
|
|
|
# Store project path for later use
|
|
echo "$project_dir" > "$MODELS_DIR/.current_project"
|
|
|
|
return 0
|
|
}
|
|
|
|
create_training_scripts() {
|
|
print_status info "Creating training helper scripts..."
|
|
|
|
local wake_word_dir=$(echo "$WAKE_WORD" | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
|
|
local project_dir="$MODELS_DIR/$wake_word_dir"
|
|
|
|
# Create recording script
|
|
cat > "$project_dir/1-record-wake-word.sh" << 'EOF'
|
|
#!/bin/bash
|
|
# Step 1: Record wake word samples
|
|
# Run this script and follow the prompts to record ~50-100 samples
|
|
|
|
eval "$(conda shell.bash hook)"
|
|
conda activate precise
|
|
|
|
echo "Recording wake word samples..."
|
|
echo "Press SPACE to start/stop recording"
|
|
echo "Press Ctrl+C when done (aim for 50-100 samples)"
|
|
echo ""
|
|
|
|
precise-collect
|
|
EOF
|
|
|
|
# Create not-wake-word recording script
|
|
cat > "$project_dir/2-record-not-wake-word.sh" << 'EOF'
|
|
#!/bin/bash
|
|
# Step 2: Record "not wake word" samples
|
|
# Record random speech, TV, music, similar-sounding phrases
|
|
|
|
eval "$(conda shell.bash hook)"
|
|
conda activate precise
|
|
|
|
echo "Recording not-wake-word samples..."
|
|
echo "Record:"
|
|
echo " - Normal conversation"
|
|
echo " - TV/music background"
|
|
echo " - Similar sounding phrases"
|
|
echo " - Ambient noise"
|
|
echo ""
|
|
echo "Press SPACE to start/stop recording"
|
|
echo "Press Ctrl+C when done (aim for 200-500 samples)"
|
|
echo ""
|
|
|
|
precise-collect -f not-wake-word/samples.wav
|
|
EOF
|
|
|
|
# Create training script
|
|
cat > "$project_dir/3-train-model.sh" << EOF
|
|
#!/bin/bash
|
|
# Step 3: Train the model
|
|
# This will train for 60 epochs (adjust -e parameter for more/less)
|
|
|
|
eval "\$(conda shell.bash hook)"
|
|
conda activate precise
|
|
|
|
echo "Training wake word model..."
|
|
echo "This will take 30-60 minutes..."
|
|
echo ""
|
|
|
|
# Train model
|
|
precise-train -e 60 ${wake_word_dir}.net .
|
|
|
|
echo ""
|
|
echo "Training complete!"
|
|
echo "Test with: precise-listen ${wake_word_dir}.net"
|
|
EOF
|
|
|
|
# Create testing script
|
|
cat > "$project_dir/4-test-model.sh" << EOF
|
|
#!/bin/bash
|
|
# Step 4: Test the model with live microphone
|
|
|
|
eval "\$(conda shell.bash hook)"
|
|
conda activate precise
|
|
|
|
echo "Testing wake word model..."
|
|
echo "Speak your wake word - you should see '!' when detected"
|
|
echo "Speak other phrases - should not trigger"
|
|
echo ""
|
|
echo "Press Ctrl+C to exit"
|
|
echo ""
|
|
|
|
precise-listen ${wake_word_dir}.net
|
|
EOF
|
|
|
|
# Create evaluation script
|
|
cat > "$project_dir/5-evaluate-model.sh" << EOF
|
|
#!/bin/bash
|
|
# Step 5: Evaluate model on test set
|
|
|
|
eval "\$(conda shell.bash hook)"
|
|
conda activate precise
|
|
|
|
echo "Evaluating wake word model on test set..."
|
|
echo ""
|
|
|
|
precise-test ${wake_word_dir}.net test/
|
|
|
|
echo ""
|
|
echo "Check metrics above:"
|
|
echo " - Wake word accuracy should be >95%"
|
|
echo " - False positive rate should be <5%"
|
|
EOF
|
|
|
|
# Create tuning script
|
|
cat > "$project_dir/6-tune-threshold.sh" << EOF
|
|
#!/bin/bash
|
|
# Step 6: Tune activation threshold
|
|
|
|
eval "\$(conda shell.bash hook)"
|
|
conda activate precise
|
|
|
|
echo "Testing different thresholds..."
|
|
echo ""
|
|
echo "Default threshold: 0.5"
|
|
echo "Higher = fewer false positives, may miss some wake words"
|
|
echo "Lower = catch more wake words, more false positives"
|
|
echo ""
|
|
|
|
for threshold in 0.3 0.5 0.7; do
|
|
echo "Testing threshold: \$threshold"
|
|
echo "Press Ctrl+C to try next threshold"
|
|
precise-listen ${wake_word_dir}.net -t \$threshold
|
|
done
|
|
EOF
|
|
|
|
# Make all scripts executable
|
|
chmod +x "$project_dir"/*.sh
|
|
|
|
print_status success "Training scripts created in $project_dir"
|
|
return 0
|
|
}
|
|
|
|
create_readme() {
|
|
print_status info "Creating README..."
|
|
|
|
local wake_word_dir=$(echo "$WAKE_WORD" | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
|
|
local project_dir="$MODELS_DIR/$wake_word_dir"
|
|
|
|
cat > "$project_dir/README.md" << EOF
|
|
# Wake Word Training: "$WAKE_WORD"
|
|
|
|
## Quick Start
|
|
|
|
Follow these steps in order:
|
|
|
|
### 1. Record Wake Word Samples
|
|
\`\`\`bash
|
|
./1-record-wake-word.sh
|
|
\`\`\`
|
|
|
|
Record 50-100 samples:
|
|
- Vary your tone and speed
|
|
- Different distances from microphone
|
|
- Different background noise levels
|
|
- Have family members record too
|
|
|
|
### 2. Record Not-Wake-Word Samples
|
|
\`\`\`bash
|
|
./2-record-not-wake-word.sh
|
|
\`\`\`
|
|
|
|
Record 200-500 samples of:
|
|
- Normal conversation
|
|
- TV/music in background
|
|
- Similar sounding phrases
|
|
- Ambient household noise
|
|
|
|
### 3. Organize Samples
|
|
|
|
Move files into training/test split:
|
|
\`\`\`bash
|
|
# 80% of wake-word samples go to:
|
|
mv wake-word-samples-* wake-word/
|
|
|
|
# 20% of wake-word samples go to:
|
|
mv wake-word-samples-* test/wake-word/
|
|
|
|
# 80% of not-wake-word samples go to:
|
|
mv not-wake-word-samples-* not-wake-word/
|
|
|
|
# 20% of not-wake-word samples go to:
|
|
mv not-wake-word-samples-* test/not-wake-word/
|
|
\`\`\`
|
|
|
|
### 4. Train Model
|
|
\`\`\`bash
|
|
./3-train-model.sh
|
|
\`\`\`
|
|
|
|
Wait 30-60 minutes for training to complete.
|
|
|
|
### 5. Test Model
|
|
\`\`\`bash
|
|
./4-test-model.sh
|
|
\`\`\`
|
|
|
|
Speak your wake word and verify detection.
|
|
|
|
### 6. Evaluate Model
|
|
\`\`\`bash
|
|
./5-evaluate-model.sh
|
|
\`\`\`
|
|
|
|
Check accuracy metrics on test set.
|
|
|
|
### 7. Tune Threshold
|
|
\`\`\`bash
|
|
./6-tune-threshold.sh
|
|
\`\`\`
|
|
|
|
Find the best threshold for your environment.
|
|
|
|
## Tips for Good Training
|
|
|
|
1. **Quality over quantity** - Clear samples are better than many poor ones
|
|
2. **Diverse conditions** - Different noise levels, distances, speakers
|
|
3. **Hard negatives** - Include similar-sounding phrases in not-wake-word set
|
|
4. **Regular updates** - Add false positives/negatives and retrain
|
|
|
|
## Next Steps
|
|
|
|
Once trained and tested:
|
|
|
|
1. Copy model to voice assistant server:
|
|
\`\`\`bash
|
|
cp ${wake_word_dir}.net ~/voice-assistant/models/
|
|
\`\`\`
|
|
|
|
2. Update voice assistant config:
|
|
\`\`\`bash
|
|
vim ~/voice-assistant/config/.env
|
|
# Set: PRECISE_MODEL=~/voice-assistant/models/${wake_word_dir}.net
|
|
\`\`\`
|
|
|
|
3. Restart voice assistant service:
|
|
\`\`\`bash
|
|
sudo systemctl restart voice-assistant
|
|
\`\`\`
|
|
|
|
## Troubleshooting
|
|
|
|
**Low accuracy?**
|
|
- Collect more training samples
|
|
- Increase training epochs (edit 3-train-model.sh, change -e 60 to -e 120)
|
|
- Verify 80/20 train/test split
|
|
|
|
**Too many false positives?**
|
|
- Increase threshold (use 6-tune-threshold.sh)
|
|
- Add false trigger audio to not-wake-word set
|
|
- Retrain with more diverse negative samples
|
|
|
|
**Misses wake words?**
|
|
- Lower threshold
|
|
- Add missed samples to training set
|
|
- Ensure good audio quality
|
|
|
|
## Resources
|
|
|
|
- Mycroft Precise Docs: https://github.com/MycroftAI/mycroft-precise
|
|
- Training Guide: https://mycroft-ai.gitbook.io/docs/mycroft-technologies/precise
|
|
- Community Models: https://github.com/MycroftAI/precise-data
|
|
EOF
|
|
|
|
print_status success "README created in $project_dir"
|
|
return 0
|
|
}
|
|
|
|
download_pretrained_models() {
|
|
print_status info "Downloading pre-trained models..."
|
|
|
|
# Create models directory
|
|
mkdir -p "$MODELS_DIR/pretrained"
|
|
|
|
# Download Hey Mycroft model (as example/base)
|
|
local model_url="https://github.com/MycroftAI/precise-data/raw/models-dev/hey-mycroft.tar.gz"
|
|
|
|
if [[ ! -f "$MODELS_DIR/pretrained/hey-mycroft.net" ]]; then
|
|
print_status info "Downloading Hey Mycroft model..."
|
|
wget -q --show-progress -O "$MODELS_DIR/pretrained/hey-mycroft.tar.gz" "$model_url" || {
|
|
print_status warning "Failed to download pre-trained model (optional)"
|
|
return 0
|
|
}
|
|
|
|
tar xzf "$MODELS_DIR/pretrained/hey-mycroft.tar.gz" -C "$MODELS_DIR/pretrained/" || {
|
|
print_status warning "Failed to extract pre-trained model"
|
|
return 0
|
|
}
|
|
|
|
print_status success "Pre-trained model downloaded"
|
|
else
|
|
print_status info "Pre-trained model already exists"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
print_next_steps() {
|
|
local wake_word_dir=$(echo "$WAKE_WORD" | tr ' ' '-' | tr '[:upper:]' '[:lower:]')
|
|
local project_dir="$MODELS_DIR/$wake_word_dir"
|
|
|
|
cat << EOF
|
|
|
|
${GREEN}Setup complete!${NC}
|
|
|
|
Wake word: "$WAKE_WORD"
|
|
Project directory: $project_dir
|
|
|
|
${BLUE}Next steps:${NC}
|
|
|
|
1. ${CYAN}Activate conda environment:${NC}
|
|
conda activate $CONDA_ENV_NAME
|
|
|
|
2. ${CYAN}Navigate to project directory:${NC}
|
|
cd $project_dir
|
|
|
|
3. ${CYAN}Follow the README or run scripts in order:${NC}
|
|
./1-record-wake-word.sh # Record wake word samples
|
|
./2-record-not-wake-word.sh # Record negative samples
|
|
# Organize samples into train/test directories
|
|
./3-train-model.sh # Train the model (30-60 min)
|
|
./4-test-model.sh # Test with microphone
|
|
./5-evaluate-model.sh # Check accuracy metrics
|
|
./6-tune-threshold.sh # Find best threshold
|
|
|
|
${BLUE}Helpful commands:${NC}
|
|
|
|
Test pre-trained model:
|
|
conda activate $CONDA_ENV_NAME
|
|
precise-listen $MODELS_DIR/pretrained/hey-mycroft.net
|
|
|
|
Check precise-engine:
|
|
precise-engine --version
|
|
|
|
${BLUE}Resources:${NC}
|
|
|
|
Full guide: See MYCROFT_PRECISE_GUIDE.md
|
|
Project README: $project_dir/README.md
|
|
Mycroft Docs: https://github.com/MycroftAI/mycroft-precise
|
|
|
|
EOF
|
|
}
|
|
|
|
# ----- Main -----
|
|
main() {
|
|
print_status info "Starting Mycroft Precise setup..."
|
|
|
|
# Parse arguments
|
|
parse_args "$@"
|
|
|
|
# Check dependencies
|
|
check_conda || exit 1
|
|
|
|
# Setup steps
|
|
create_conda_environment || exit 1
|
|
install_tensorflow || exit 1
|
|
install_precise || exit 1
|
|
download_precise_engine || exit 1
|
|
create_training_directory || exit 1
|
|
create_training_scripts || exit 1
|
|
create_readme || exit 1
|
|
download_pretrained_models || exit 1
|
|
|
|
# Print next steps
|
|
print_next_steps
|
|
}
|
|
|
|
# Run main
|
|
main "$@"
|