#!/usr/bin/env bash set -eu title='Wyoming Satellite' # Directory of *this* script this_dir="$( cd "$( dirname "$0" )" && pwd )" program_dir="$(realpath "${this_dir}/..")" # Directory to store stuff that won't interfere with git local_dir="${program_dir}/local" mkdir -p "${local_dir}" debug_enabled='' system_deps='' satellite_name='' satellite_type='' wake_word_system='' wake_word='' custom_wake_word_dir="${local_dir}/custom-wake-words" mkdir -p "${custom_wake_word_dir}" noise_suppression='' microphone_command='arecord -q -r 16000 -c 1 -f S16_LE -t raw' speaker_command='aplay -q -r 22050 -c 1 -f S16_LE -t raw' speaker_sounds=() pushd () { command pushd "$@" > /dev/null } popd () { command popd "$@" > /dev/null } # Args for arg in "$@"; do if [ "${arg}" = '--debug' ]; then debug_enabled='1' fi done if [ -n "${debug_enabled}" ]; then whiptail --title "${title}" --msgbox \ 'Debug mode is enabled. This will cause all services to log verbosely, and audio to be recorded in local/debug-recording. Make sure to re-run the installer without debug mode before deploying the satellite.' \ 10 75 fi # ----------------------------------------------------------------------------- # System dependencies # ----------------------------------------------------------------------------- commands=('whiptail' 'git' 'python3' 'pip3') for command in "${commands[@]}"; do if ! command -v "${command}" &> /dev/null; then system_deps='1' break fi done if command -v python3 &> /dev/null; then if ! python3 -c 'import venv'; then system_deps='1' fi fi if [ -n "${system_deps}" ]; then echo 'Installing system dependencies' sudo apt-get update sudo apt-get install --yes --no-install-recommends \ whiptail python3-venv python3-pip git fi # ----------------------------------------------------------------------------- # Microphone # ----------------------------------------------------------------------------- function get_microphone { microphones=() while read -r microphone; do microphones+=("${microphone}" "${microphone}" 0) done < <(arecord -L | grep 'plughw:') whiptail --title "${title}" --notags \ --radiolist 'Select Microphone:' 20 70 \ 12 "${microphones[@]}" \ 'custom' 'Custom ALSA Device' 0 \ 'respeaker' 'Install ReSpeaker Drivers' 0 } function get_custom_microphone { whiptail --title "${title}" --inputbox \ 'ALSA Input Device Name:' \ 8 75 } microphone='' while [ -z "${microphone}" ]; do microphone="$(get_microphone 3>&1 1>&2 2>&3)" done if [ "${microphone}" = 'custom' ]; then # Custom ALSA device name from "arecord -L" microphone='' while [ -z "${microphone}" ]; do microphone="$(get_custom_microphone 3>&1 1>&2 2>&3)" done elif [ "${microphone}" = 'respeaker' ]; then # ReSpeaker driver installation (requires reboot) whiptail --title "${title}" --yesno \ 'ReSpeaker drivers for the Raspberry Pi will now be compiled and installed. This will take a while and require a reboot. Continue?' \ 8 75 echo 'Installing ReSpeaker drivers' "${program_dir}/etc/install-respeaker-drivers.sh" whiptail --title "${title}" --msgbox \ 'Driver installation complete. The system will now reboot. Re-run the installer and select the "seeed" microphone.' \ 8 75 sudo reboot fi # Add device microphone_command="${microphone_command} -D ${microphone}" # ----------------------------------------------------------------------------- # Speaker # ----------------------------------------------------------------------------- function get_speaker { speakers=() while read -r speaker; do speakers+=("${speaker}" "${speaker}" 0) done < <(aplay -L | grep 'plughw:') whiptail --title "${title}" --notags \ --radiolist 'Select Speaker:' 20 70 \ 12 "${speakers[@]}" \ 'custom' 'Custom ALSA Device' 0 \ 'no_speaker' 'No Audio Output' 0 } function get_custom_speaker { whiptail --title "${title}" --inputbox \ 'ALSA Output Device Name:' \ 8 75 } function get_speaker_sounds { whiptail --title "${title}" --checklist \ 'Play Sounds?' \ 10 50 2 \ 'awake' 'On wake-up' 0 \ 'done' 'After voice command' 0 } speaker='' while [ -z "${speaker}" ]; do speaker="$(get_speaker 3>&1 1>&2 2>&3)" done if [ "${speaker}" = 'custom' ]; then # Custom ALSA device name from "aplay -L" speaker='' while [ -z "${speaker}" ]; do speaker="$(get_custom_speaker 3>&1 1>&2 2>&3)" done fi if [ "${speaker}" = 'no_speaker' ]; then # No audio output speaker_command='' else # Add device speaker_command="${speaker_command} -D ${speaker}" # awake/done sounds speaker_sounds="$(get_speaker_sounds 3>&1 1>&2 2>&3)" fi # ----------------------------------------------------------------------------- # wyoming-satellite # ----------------------------------------------------------------------------- if [ ! -d "${program_dir}/wyoming_satellite" ]; then if [ ! -d "${this_dir}/wyoming-satellite" ]; then # Download source here git clone 'https://github.com/rhasspy/wyoming-satellite.git' fi program_dir="${this_dir}/wyoming-satellite" fi # Virtual environment venv="${program_dir}/.venv" pip="${venv}/bin/pip3" if [ ! -d "${venv}" ]; then echo 'Creating virtual environment' "${program_dir}/script/setup" fi # Satellite name function get_satellite_name { whiptail --title "${title}" --inputbox \ 'Satellite Name:' \ 8 75 \ 'Wyoming Satellite' } while [ -z "${satellite_name}" ]; do satellite_name="$(get_satellite_name 3>&1 1>&2 2>&3)" done # ----------------------------------------------------------------------------- # Satellite type # ----------------------------------------------------------------------------- function get_satellite_type { whiptail --title "${title}" --notags \ --radiolist 'Select Satellite Type:' 10 50 \ 3 \ always 'Always streaming' 1 \ vad 'Voice activity detection' 0 \ wake 'Local wake word detection' 0 } function get_wake_word_system { # TODO: Support porcupine1 and snowboy echo 'openWakeWord' # whiptail --title "${title}" --notags \ # --radiolist 'Select Wake Word System:' 10 50 \ # 3 \ # openWakeWord 'openWakeWord' 1 \ # porcupine1 'porcupine1' 0 \ # snowboy 'snowboy' 0 } function install_openWakeWord { if [ ! -d '/usr/share/doc/libopenblas-dev' ]; then echo 'Installing system dependencies' sudo apt-get install --yes --no-install-recommends \ libopenblas-dev fi oww_dir="${local_dir}/wyoming-openwakeword" if [ ! -d "${oww_dir}" ]; then echo 'Installing openWakeWord' # Download pushd "${local_dir}" git clone 'https://github.com/rhasspy/wyoming-openwakeword.git' popd # Install "${oww_dir}/script/setup" fi } function list_openWakeWord { whiptail --title "${title}" --notags \ --radiolist 'Select Wake Word:' 15 50 \ 6 \ ok_nabu 'ok nabu' 1 \ hey_jarvis 'hey jarvis' 0 \ alexa 'alexa' 0 \ hey_rhasspy 'hey rhasspy' 0 \ hey_mycroft 'hey mycroft' 0 \ community 'Community Wake Words' 0 } satellite_type="$(get_satellite_type 3>&1 1>&2 2>&3)" if [ "${satellite_type}" = 'vad' ]; then # Install vad requirements "${pip}" install \ --extra-index-url 'https://www.piwheels.org/simple' \ -f 'https://synesthesiam.github.io/prebuilt-apps/' \ -r "${program_dir}/requirements_vad.txt" elif [ "${satellite_type}" = 'wake' ]; then # Install openWakeWord wake_word_system="$(get_wake_word_system 3>&1 1>&2 2>&3)" if [ "${wake_word_system}" = 'openWakeWord' ]; then install_openWakeWord mkdir -p "${custom_wake_word_dir}/openWakeWord" # Choose wake word wake_word="$(list_openWakeWord 3>&1 1>&2 2>&3)" if [ "${wake_word}" = 'community' ]; then # Download community wake words community_wake_word_dir="${local_dir}/home-assistant-wakewords-collection" if [ ! -d "${community_wake_word_dir}" ]; then echo 'Downloading community wake words' pushd "${local_dir}" git clone 'https://github.com/fwartner/home-assistant-wakewords-collection.git' popd fi # Select community wake word community_wake_words=() pushd "${community_wake_word_dir}" while read -r wake_word_model; do wake_word_name="$(basename "${wake_word_model}" .tflite)" community_wake_words+=("${wake_word_model}" "${wake_word_name}" 0) done < <(find . -name '*.tflite' -type f | sort) popd function list_community_wake_words { whiptail --title "${title}" --notags \ --radiolist 'Select Wake Word:' 25 80 \ 20 ${community_wake_words[@]} } wake_word="$(list_community_wake_words 3>&1 1>&2 2>&3)" cp "${community_wake_word_dir}/${wake_word}" \ "${custom_wake_word_dir}/openWakeWord/" wake_word="$(basename "${wake_word}" .tflite)" fi # community wake word fi # openWakeWord fi # local wake word detection # ----------------------------------------------------------------------------- # Audio enhancements # ----------------------------------------------------------------------------- function get_noise_suppression { whiptail --title "${title}" --notags --radiolist \ 'Noise Suppression Level:' \ 15 75 6 \ '' 'Off' 1 \ '1' 'Low' 0 \ '2' 'Medium' 0 \ '3' 'High' 0 \ '4' 'Max' 0 } noise_suppression="$(get_noise_suppression 3>&1 1>&2 2>&3)" if [ -n "${noise_suppression}" ]; then # Install audio enhancement requirements "${pip}" install \ --extra-index-url 'https://www.piwheels.org/simple' \ -f 'https://synesthesiam.github.io/prebuilt-apps/' \ -r "${program_dir}/requirements_audio_enhancement.txt" fi # ----------------------------------------------------------------------------- # Services # ----------------------------------------------------------------------------- services_dir="${local_dir}/services" mkdir -p "${services_dir}" wake_service_requires='' event_service_requires='' # Disable any previous services echo 'Stopping previous services' for service in 'wyoming-satellite' 'wyoming-openwakeword'; do sudo systemctl disable --now "${service}.service" 2>/dev/null || true done echo 'Creating systemd unit files' new_services=('wyoming-satellite.service') # openWakeWord if [ "${wake_word_system}" = 'openWakeWord' ]; then new_services+=('wyoming-openwakeword.service') wake_service_requires='Requires=wyoming-openwakeword.service' wake_word_command=("${local_dir}/wyoming-openwakeword/script/run") wake_word_command+=('--uri' 'tcp://127.0.0.1:10400') wake_word_command+=('--custom-model-dir' "'${custom_wake_word_dir}/openWakeWord'") if [ -n "${debug_enabled}" ]; then wake_word_command+=('--debug') fi cat > "${services_dir}/wyoming-openwakeword.service" < "${services_dir}/wyoming-satellite.service" <