From ac8be3c4b5d24e0005b5bf7ca70882eb85491c78 Mon Sep 17 00:00:00 2001 From: legop3 Date: Wed, 1 Jul 2026 12:49:23 -0400 Subject: [PATCH] installer splitup and laptop alsa stuff --- dist/roverd-debian-laptop | Bin 6971684 -> 6971684 bytes pi/asound.debian-laptop.conf | 88 ++++ pi/install/common.sh | 79 +++ pi/install/debian_laptop_profile.sh | 42 ++ pi/install/media_env.sh | 89 ++++ pi/install/pi_profile.sh | 160 ++++++ pi/install/profiles.sh | 105 ++++ pi/install/self_update.sh | 38 ++ pi/install/systemd.sh | 43 ++ pi/install_roverd.sh | 541 ++------------------- pi/roverd/roverd.debian-laptop.sample.yaml | 16 +- to-do.md | 7 +- 12 files changed, 700 insertions(+), 508 deletions(-) create mode 100644 pi/asound.debian-laptop.conf create mode 100644 pi/install/common.sh create mode 100644 pi/install/debian_laptop_profile.sh create mode 100644 pi/install/media_env.sh create mode 100644 pi/install/pi_profile.sh create mode 100644 pi/install/profiles.sh create mode 100644 pi/install/self_update.sh create mode 100644 pi/install/systemd.sh diff --git a/dist/roverd-debian-laptop b/dist/roverd-debian-laptop index 4f1f099120608eae668b5107c03b80985bd54073..c0493c2fd85454d0ce7d27fdfff4e6936fd32407 100755 GIT binary patch delta 687 zcmbWx$xah-0D$qqRu;FS;*N?Sf+Ea+mM&AVTJ5xKEv48NT+kW*t!+bbfkJ3ZJb36M z@B&;+G%?}OqY3c=d;v{3H{LvY;NulM{ElDp?d*N_?BLsq?_;e}tJ|`!Z*FZm<$TPa zU7lZ8>x)|(DZTPW6yLwA<>L7T_pPB;WhqlAMeSs~qK{TLtQDm0aN|d@-T*ejj;wrA;I{I(}{TRSa4B{4s zFpS%{gAv@tD8`^dK!XkgCj1B>h!8A!F}#%79~9LKAll=6_1TEz$UUKVHxmX<^p2Z#O# zG8i`&6HQ1MI+C~>AA=yZ-f4XdN5n@Y`Ccpp|RhqL~?6kzzGo zEv^<+?e+Ll#?IwMZNaWir|V0p<+NJyO1580nG@NC=s=}z`^7>|eN$O}<)^Dti_#jM zE!VTvz>6zwilTHO1Zy&{bZ(D)|K0KDr~ke+<9zry_v2f)rk3ZOm{i}j!i`pV7j`3r zHtaz=_M!v(upb9-5QlIW|JqI*!BHGT7rJpACvXy{a2jXOgR?k?^SFSExP;5Nf?o9D zD*AB^*Kq?kF@Qm+h(JRW0unl6h{M1T61auixPv5yF@hA*$Y8YD3Y(t?`=dtGb%d`8 zUDpj62-gt0BV9Kba^ew9YQ8kQnBfO&TS>F0T_l7|L>fCu6Iocei+dPD4&#`>B&Kj5 z)3EUXGsxp13V4Lan8g!3#WOs|9A2OZ2X1pG>HS*yukAIq)rPg{!AF3XC^fCk`Ry-% E0TW3Ol>h($ diff --git a/pi/asound.debian-laptop.conf b/pi/asound.debian-laptop.conf new file mode 100644 index 00000000..3795ff1e --- /dev/null +++ b/pi/asound.debian-laptop.conf @@ -0,0 +1,88 @@ +# ALSA routing for the Debian laptop rover profile. +# +# This file intentionally mirrors the logical device names used by the Pi rover +# audio setup. roverd can keep sending horn audio to "horn", forwarded browser +# audio to "forward", and TTS to ALSA's default playback path without caring +# which physical sound card is underneath the profile. + +pcm.roverd_playback { + type plug + + # Use the system's first normal ALSA playback device as the physical sink. + # This avoids referencing "default" here, because this file replaces + # pcm.!default below and using it as a slave would recurse. + slave.pcm "sysdefault" +} + +pcm.roverd_capture { + type plug + + # The media publisher records from "default"; with pcm.!default below that + # capture side resolves here. Keeping capture separate from playback lets the + # asym default expose ordinary microphone input while playback goes through + # the TTS softvol path. + slave.pcm "sysdefault" +} + +pcm.tts_softvol { + type softvol + slave.pcm "roverd_playback" + control { + name "TTSMaster" + card 0 + } + min_dB -60.0 + max_dB 12.0 +} + +pcm.horn_softvol { + type softvol + slave.pcm "roverd_playback" + control { + name "HornMaster" + card 0 + } + min_dB -60.0 + max_dB 12.0 +} + +pcm.forward_softvol { + type softvol + slave.pcm "roverd_playback" + control { + name "ForwardMaster" + card 0 + } + min_dB -60.0 + max_dB 12.0 +} + +pcm.tts { + type plug + slave.pcm "tts_softvol" +} + +pcm.horn { + type plug + slave.pcm "horn_softvol" +} + +pcm.forward { + type plug + slave.pcm "forward_softvol" +} + +pcm.!default { + type asym + + # Existing TTS engines play to their default ALSA output, so default playback + # is intentionally the TTS path. This preserves the current TTS execution + # model while still making the TTS volume control meaningful on laptops. + playback.pcm "tts" + capture.pcm "roverd_capture" +} + +ctl.!default { + type hw + card 0 +} diff --git a/pi/install/common.sh b/pi/install/common.sh new file mode 100644 index 00000000..37e8b689 --- /dev/null +++ b/pi/install/common.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +CONFIG_DEST="/etc/roverd.yaml" + +log() { + echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" +} + +ensure_user() { + local user="$1" + local groups="${2:-}" + local existing_groups="" + local group + + if [[ -n "$groups" ]]; then + IFS=',' read -ra group_list <<< "$groups" + for group in "${group_list[@]}"; do + if getent group "$group" >/dev/null 2>&1; then + existing_groups="${existing_groups:+$existing_groups,}$group" + else + # Different Debian-family installs expose slightly different + # hardware groups. Skipping missing optional groups lets one + # profile script cover ordinary laptops and Raspberry Pi OS. + log "Skipping missing system group '$group' for $user" + fi + done + fi + + if ! id -u "$user" >/dev/null 2>&1; then + if [[ -n "$existing_groups" ]]; then + useradd -r -s /usr/sbin/nologin -G "$existing_groups" "$user" + else + useradd -r -s /usr/sbin/nologin "$user" + fi + elif [[ -n "$existing_groups" ]]; then + usermod -a -G "$existing_groups" "$user" + fi +} + +require_root() { + if [[ "${EUID}" -ne 0 ]]; then + echo "Please run as root (sudo)" >&2 + exit 1 + fi +} + +validate_install_inputs() { + if [[ ! -f "$BINARY_SRC" ]]; then + echo "Binary not found at $BINARY_SRC" >&2 + exit 1 + fi + + if [[ ! -f "$CONFIG_SRC" ]]; then + echo "Config source not found at $CONFIG_SRC" >&2 + exit 1 + fi +} + +install_roverd_binary() { + ensure_user roverd "$ROVERD_GROUPS" + install -o roverd -g roverd -m 0755 "$BINARY_SRC" /usr/local/bin/roverd + log "Installed roverd binary for profile $PROFILE" +} + +install_roverd_config() { + CONFIG_EXISTS=0 + if [[ -f "$CONFIG_DEST" ]]; then + CONFIG_EXISTS=1 + log "Existing $CONFIG_DEST found; leaving it in place" + else + install -D -o roverd -g roverd -m 0640 "$CONFIG_SRC" "$CONFIG_DEST" + log "Installed sample config to $CONFIG_DEST (edit before starting service)" + fi +} + +install_roverd_unit() { + install -m 0644 pi/systemd/roverd.service /etc/systemd/system/roverd.service + log "Installed roverd systemd unit" +} diff --git a/pi/install/debian_laptop_profile.sh b/pi/install/debian_laptop_profile.sh new file mode 100644 index 00000000..3276bd00 --- /dev/null +++ b/pi/install/debian_laptop_profile.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +install_debian_laptop_deps() { + # This profile is deliberately Debian-only. Using apt directly is simpler + # than adding a fake cross-distro layer, and it keeps the installed package + # set easy to inspect on the actual rover laptop. + if command -v ffmpeg >/dev/null 2>&1 \ + && command -v arecord >/dev/null 2>&1 \ + && command -v aplay >/dev/null 2>&1 \ + && command -v amixer >/dev/null 2>&1 \ + && command -v v4l2-ctl >/dev/null 2>&1 \ + && command -v flite >/dev/null 2>&1 \ + && command -v espeak >/dev/null 2>&1; then + log "Debian laptop media/audio dependencies already installed; skipping apt install" + return + fi + + log "Installing Debian laptop dependencies (ffmpeg, ALSA tools, V4L2 tools, flite, espeak)..." + apt-get update + apt-get install -y --no-install-recommends ffmpeg alsa-utils v4l-utils ca-certificates flite espeak +} + +install_debian_laptop_audio_support() { + if [[ ! -f pi/asound.debian-laptop.conf ]]; then + log "WARNING: pi/asound.debian-laptop.conf missing; skipping Debian laptop ALSA config install" + return + fi + + # The laptop profile still uses roverd's existing audio contract: TTS plays + # to ALSA's default output, horn plays to the named "horn" device, and + # forwarded web audio plays to the named "forward" device. Installing one + # profile-specific asound.conf gives those paths independent softvol mixer + # controls without changing the TTS runtime code. + install -m 0644 pi/asound.debian-laptop.conf /etc/asound.conf + log "Installed Debian laptop ALSA config to /etc/asound.conf" + log "ALSA config updated; restarting audio clients or rebooting is recommended before testing laptop audio" +} + +install_debian_laptop_profile() { + install_debian_laptop_deps + install_debian_laptop_audio_support +} diff --git a/pi/install/media_env.sh b/pi/install/media_env.sh new file mode 100644 index 00000000..8c56d206 --- /dev/null +++ b/pi/install/media_env.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +write_media_env_placeholder() { + install -d -o roverd -g roverd /var/lib/roverd + + case "$PROFILE" in + pi) + # roverd rewrites media.env after loading /etc/roverd.yaml. These + # values only make the services syntactically usable before the first + # successful roverd run, so they mirror the Pi defaults instead of + # trying to become a second source of truth. + cat > /var/lib/roverd/media.env <<'ENV' +# Managed by roverd; placeholder values will be overwritten at runtime. +ROVERD_VIDEO_ENABLE=1 +ROVERD_VIDEO_PUBLISHER=pi-libcamera +ROVERD_VIDEO_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316 +ROVERD_VIDEO_DEVICE= +ROVERD_VIDEO_INPUT_FORMAT= +ROVERD_VIDEO_WIDTH=640 +ROVERD_VIDEO_HEIGHT=480 +ROVERD_VIDEO_FPS=30 +ROVERD_VIDEO_BITRATE=2000000 +ROVERD_VIDEO_INVERT=1 +ROVERD_VIDEO_SENSOR_MODE=1296:972 +ROVERD_AUDIO_CAPTURE_ENABLE=0 +ROVERD_AUDIO_CAPTURE_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME-audio,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316 +ROVERD_AUDIO_CAPTURE_DEVICE=hw:0,0 +ROVERD_AUDIO_CAPTURE_SAMPLE_RATE=48000 +ROVERD_AUDIO_CAPTURE_CHANNELS=2 +ROVERD_AUDIO_CAPTURE_BITRATE=510000 +ROVERD_AUDIO_PLAYBACK_ENABLE=1 +ROVERD_AUDIO_PLAYBACK_FORWARD_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME-fwd,m=request&latency=10&mode=caller&transtype=live&pkt_size=1316 +ROVERD_AUDIO_PLAYBACK_DEVICE=forward +ROVERD_AUDIO_PLAYBACK_NORMALIZE=1 +ROVERD_AUDIO_PLAYBACK_NORMALIZE_FILTER=dynaudnorm=f=75:g=15:m=10:p=0.9,alimiter=limit=0.85:level=disabled +ENV + ;; + debian-laptop) + # The laptop placeholder uses the same logical ALSA names as the + # installed laptop asound.conf. That keeps forwarded audio under the + # ForwardMaster mixer control while TTS and horn keep their own paths. + cat > /var/lib/roverd/media.env <<'ENV' +# Managed by roverd; placeholder values will be overwritten at runtime. +ROVERD_VIDEO_ENABLE=1 +ROVERD_VIDEO_PUBLISHER=debian-laptop-v4l2 +ROVERD_VIDEO_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316 +ROVERD_VIDEO_DEVICE=/dev/video0 +ROVERD_VIDEO_INPUT_FORMAT=mjpeg +ROVERD_VIDEO_WIDTH=640 +ROVERD_VIDEO_HEIGHT=480 +ROVERD_VIDEO_FPS=30 +ROVERD_VIDEO_BITRATE=2000000 +ROVERD_VIDEO_INVERT=0 +ROVERD_VIDEO_SENSOR_MODE= +ROVERD_AUDIO_CAPTURE_ENABLE=1 +ROVERD_AUDIO_CAPTURE_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME-audio,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316 +ROVERD_AUDIO_CAPTURE_DEVICE=default +ROVERD_AUDIO_CAPTURE_SAMPLE_RATE=48000 +ROVERD_AUDIO_CAPTURE_CHANNELS=2 +ROVERD_AUDIO_CAPTURE_BITRATE=510000 +ROVERD_AUDIO_PLAYBACK_ENABLE=1 +ROVERD_AUDIO_PLAYBACK_FORWARD_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME-fwd,m=request&latency=10&mode=caller&transtype=live&pkt_size=1316 +ROVERD_AUDIO_PLAYBACK_DEVICE=forward +ROVERD_AUDIO_PLAYBACK_NORMALIZE=1 +ROVERD_AUDIO_PLAYBACK_NORMALIZE_FILTER=dynaudnorm=f=75:g=15:m=10:p=0.9,alimiter=limit=0.85:level=disabled +ENV + ;; + esac + + chown roverd:roverd /var/lib/roverd/media.env + chmod 0640 /var/lib/roverd/media.env +} + +install_audio_fifo() { + local fifo_path="/var/lib/roverd/audio.pcm" + + # The capture service and publisher service meet at this FIFO. Recreating + # it only when it is missing or the path is not a FIFO preserves a working + # service pipe while still fixing accidental regular-file leftovers. + if [[ -p "$fifo_path" ]]; then + chown roverd:audio "$fifo_path" + chmod 0660 "$fifo_path" + else + rm -f "$fifo_path" + mkfifo "$fifo_path" + chown roverd:audio "$fifo_path" + chmod 0660 "$fifo_path" + fi +} diff --git a/pi/install/pi_profile.sh b/pi/install/pi_profile.sh new file mode 100644 index 00000000..599f114c --- /dev/null +++ b/pi/install/pi_profile.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +install_pi_video_deps() { + if command -v ffmpeg >/dev/null 2>&1 && (command -v rpicam-vid >/dev/null 2>&1 || command -v libcamera-vid >/dev/null 2>&1); then + log "Video dependencies already installed; skipping apt install" + return + fi + log "Installing video dependencies (libcamera-apps, ffmpeg)..." + apt-get update + apt-get install -y --no-install-recommends libcamera-apps ffmpeg +} + +find_boot_config() { + if [[ -f /boot/firmware/config.txt ]]; then + printf "/boot/firmware/config.txt" + return 0 + fi + if [[ -f /boot/config.txt ]]; then + printf "/boot/config.txt" + return 0 + fi + return 1 +} + +ensure_pwm_overlay() { + local boot_config + if ! boot_config="$(find_boot_config)"; then + log "WARNING: unable to locate /boot config.txt; please ensure dtoverlay=pwm-2chan is added manually for servo support" + return + fi + if grep -Eq '^\s*dtoverlay=pwm(-2chan)?' "$boot_config"; then + log "PWM overlay already present in $boot_config" + return + fi + local backup="${boot_config}.roverd.$(date +%Y%m%d%H%M%S).bak" + cp "$boot_config" "$backup" + { + echo "" + echo "# Added by roverd installer to expose PWM hardware for camera servo control on GPIO12/13 (leaves GPIO18/19 free for I2S)" + echo "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4" + } >> "$boot_config" + log "Enabled dtoverlay=pwm-2chan on GPIO12/13 in $boot_config (backup at $backup). Reboot required for changes to apply." +} + +install_pi_audio_support() { + local boot_config + if ! boot_config="$(find_boot_config)"; then + log "WARNING: unable to locate /boot config.txt; please enable googlevoicehat-soundcard overlay manually" + else + # Ensure onboard audio is disabled (prevents card index flapping) + if grep -Eq '^\s*dtparam=audio=on\b' "$boot_config"; then + log "Disabling onboard audio (dtparam=audio=on -> off) in $boot_config" + sed -i 's/^\s*dtparam=audio=on\b/# roverd disabled onboard audio\ndtparam=audio=off/' "$boot_config" + fi + if ! grep -Eq '^\s*dtparam=audio=off\b' "$boot_config"; then + log "Adding dtparam=audio=off to $boot_config" + echo "dtparam=audio=off" >> "$boot_config" + fi + if ! grep -Eq '^\s*dtparam=i2s=on\b' "$boot_config"; then + log "Adding dtparam=i2s=on to $boot_config" + echo "dtparam=i2s=on" >> "$boot_config" + fi + if ! grep -Eq '^\s*dtoverlay=googlevoicehat-soundcard\b' "$boot_config"; then + local backup="${boot_config}.roverd.$(date +%Y%m%d%H%M%S).bak" + cp "$boot_config" "$backup" + { + echo "" + echo "# Added by roverd installer to enable Google AIY v1 sound card" + echo "dtoverlay=googlevoicehat-soundcard" + } >> "$boot_config" + log "Enabled googlevoicehat-soundcard overlay in $boot_config (backup at $backup). Reboot required." + else + log "googlevoicehat-soundcard overlay already present in $boot_config" + fi + fi + if [[ -f pi/asound.conf ]]; then + install -m 0644 pi/asound.conf /etc/asound.conf + log "Installed ALSA config to /etc/asound.conf" + alsa_reload_notice=1 + else + log "WARNING: pi/asound.conf missing; skipping ALSA config install" + fi + + if [[ "${alsa_reload_notice:-0}" -eq 1 ]]; then + log "ALSA config updated; reboot recommended for overlay + audio changes" + fi + + log "Installing TTS/audio packages (flite, espeak, Chrome TTS runtime deps)..." + if command -v flite >/dev/null 2>&1 \ + && command -v espeak >/dev/null 2>&1 \ + && command -v python3 >/dev/null 2>&1 \ + && command -v curl >/dev/null 2>&1 \ + && command -v xz >/dev/null 2>&1 \ + && command -v unzip >/dev/null 2>&1 \ + && command -v aplay >/dev/null 2>&1 \ + && ldconfig -p 2>/dev/null | grep -q 'libc++\.so\.1' \ + && ldconfig -p 2>/dev/null | grep -q 'libc++abi\.so\.1'; then + log "Core TTS packages already installed; skipping apt install" + else + apt-get update + apt-get install -y --no-install-recommends flite espeak python3 curl xz-utils unzip alsa-utils libc++1 libc++abi1 \ + || apt-get install -y --no-install-recommends flite espeak python3 curl xz-utils unzip alsa-utils libc++1-14 libc++abi1-14 + fi + + install -D -o root -g root -m 0755 pi/bin/chromegtts-daemon.py /usr/local/bin/chromegtts-daemon + log "Installed chromegtts daemon" + + install_google_tts_assets +} + +install_google_tts_assets() { + local asset_dir="/opt/roverd/googletts" + local voice_dir="${asset_dir}/en-us-x-multi-r30" + local dist_url="https://storage.googleapis.com/chromeos-localmirror/distfiles/googletts-26.5.tar.xz" + local tmp_dir + local lib_member + + case "$(uname -m)" in + aarch64|arm64) + lib_member="libchrometts_arm64.so" + ;; + armv7l|armhf) + lib_member="libchrometts_armv7.so" + ;; + *) + log "WARNING: unsupported Chrome TTS architecture $(uname -m); skipping Google TTS assets" + return + ;; + esac + + if [[ -f "${asset_dir}/libchrometts.so" && -f "${voice_dir}/pipeline.pb" ]]; then + log "Google Chrome TTS assets already installed; skipping download" + return + fi + + tmp_dir="$(mktemp -d)" + log "Downloading Google Chrome TTS assets..." + curl -L -o "${tmp_dir}/googletts-26.5.tar.xz" "$dist_url" + tar -xf "${tmp_dir}/googletts-26.5.tar.xz" -C "$tmp_dir" en-us-x-multi.zvoice "$lib_member" + + install -d -o root -g root -m 0755 "$asset_dir" + install -o root -g root -m 0644 "${tmp_dir}/${lib_member}" "${asset_dir}/libchrometts.so" + rm -rf "$voice_dir" + install -d -o root -g root -m 0755 "$voice_dir" + unzip -q "${tmp_dir}/en-us-x-multi.zvoice" -d "$voice_dir" + chown -R root:root "$asset_dir" + find "$asset_dir" -type d -exec chmod 0755 {} + + find "$asset_dir" -type f -exec chmod 0644 {} + + rm -rf "$tmp_dir" + log "Installed Google Chrome TTS assets to $asset_dir" +} + +install_pi_profile() { + if ! command -v rpicam-vid >/dev/null 2>&1 && ! command -v libcamera-vid >/dev/null 2>&1; then + log "WARNING: neither rpicam-vid nor libcamera-vid found in PATH; install libcamera-apps." + fi + install_pi_video_deps + ensure_pwm_overlay + install_pi_audio_support +} diff --git a/pi/install/profiles.sh b/pi/install/profiles.sh new file mode 100644 index 00000000..7245974a --- /dev/null +++ b/pi/install/profiles.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +PROFILE="pi" +BINARY_SRC="" +CONFIG_SRC="" + +usage() { + cat <<'USAGE' +Usage: sudo ./pi/install_roverd.sh [options] + +Options: + --profile Install profile: pi or debian-laptop (default: pi) + --debian-laptop Shortcut for --profile debian-laptop + -b, --binary Path to the roverd binary (default depends on profile) + -c, --config Source config to install if /etc/roverd.yaml is missing + (default depends on profile) + -h, --help Show this help text + +The script must run from the repository root and as root (sudo). It will: + * create system users/groups if needed + * install /usr/local/bin/roverd and /etc/roverd.yaml + * install profile-specific video helpers and shared audio helpers + * install profile-specific ALSA/system audio setup + * install the fixed-command self-update helper used by admin-triggered updates + * enable roverd.service and media publisher/listener services +USAGE +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --profile) + PROFILE="${2:-}" + shift 2 + ;; + --debian-laptop) + PROFILE="debian-laptop" + shift + ;; + -b|--binary) + BINARY_SRC="${2:-}" + shift 2 + ;; + -c|--config) + CONFIG_SRC="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + esac + done +} + +select_profile() { + case "$PROFILE" in + pi) + BINARY_SRC="${BINARY_SRC:-dist/roverd}" + CONFIG_SRC="${CONFIG_SRC:-pi/roverd/roverd.sample.yaml}" + VIDEO_HELPER_SRC="pi/bin/video-publisher.sh" + VIDEO_HELPER_DEST="/usr/local/bin/video-publisher" + VIDEO_SERVICE_SRC="pi/systemd/video-publisher.service" + VIDEO_SERVICE_NAME="video-publisher.service" + ROVERD_GROUPS="dialout,gpio,video,render,audio" + ;; + debian-laptop) + BINARY_SRC="${BINARY_SRC:-dist/roverd-debian-laptop}" + CONFIG_SRC="${CONFIG_SRC:-pi/roverd/roverd.debian-laptop.sample.yaml}" + VIDEO_HELPER_SRC="pi/bin/debian-laptop-video-publisher.sh" + VIDEO_HELPER_DEST="/usr/local/bin/debian-laptop-video-publisher" + VIDEO_SERVICE_SRC="pi/systemd/debian-laptop-video-publisher.service" + VIDEO_SERVICE_NAME="debian-laptop-video-publisher.service" + ROVERD_GROUPS="dialout,video,render,audio" + ;; + *) + echo "Unknown profile: $PROFILE" >&2 + usage + exit 1 + ;; + esac +} + +install_selected_profile() { + case "$PROFILE" in + pi) + # The Pi profile owns the board-specific camera, PWM, and AIY audio + # setup. Keeping that hardware work behind this dispatcher prevents + # the laptop profile from accidentally inheriting Pi overlays or + # card-index assumptions. + install_pi_profile + ;; + debian-laptop) + # The Debian laptop profile only installs ordinary Debian packages + # and a laptop ALSA routing file. Anything that depends on Pi GPIO, + # overlays, or the Google Voice HAT stays out of this path. + install_debian_laptop_profile + ;; + esac +} diff --git a/pi/install/self_update.sh b/pi/install/self_update.sh new file mode 100644 index 00000000..0dc8276a --- /dev/null +++ b/pi/install/self_update.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +install_self_update_support() { + local sudoers_file="/etc/sudoers.d/roverd-self-update" + local update_env="/etc/roverd-update.env" + local quoted_repo_root + + # The self-update helper must know which checkout should receive the git + # pull. Recording the repository root during the normal installer run keeps + # the runtime websocket command simple and prevents the rover from accepting + # a caller-controlled path. + printf -v quoted_repo_root '%q' "$REPO_ROOT" + install -D -o root -g root -m 0644 /dev/null "$update_env" + cat > "$update_env" < "$sudoers_file" <<'SUDOERS' +# Managed by pi/install_roverd.sh. +# Allow only the roverd service account to run the fixed self-update helper. +roverd ALL=(root) NOPASSWD: /usr/local/sbin/roverd-self-update +SUDOERS + chown root:root "$sudoers_file" + chmod 0440 "$sudoers_file" + if command -v visudo >/dev/null 2>&1; then + visudo -cf "$sudoers_file" >/dev/null + fi + log "Installed roverd self-update helper and sudoers rule" +} diff --git a/pi/install/systemd.sh b/pi/install/systemd.sh new file mode 100644 index 00000000..a39bcb0f --- /dev/null +++ b/pi/install/systemd.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +install_media_units() { + # Video publishing is the only profile-specific media service right now: + # Pi uses libcamera/rpicam, while Debian laptops publish from a V4L2 webcam. + install -D -o root -g root -m 0755 "$VIDEO_HELPER_SRC" "$VIDEO_HELPER_DEST" + log "Installed $PROFILE video publisher helper" + install -m 0644 "$VIDEO_SERVICE_SRC" "/etc/systemd/system/$VIDEO_SERVICE_NAME" + log "Installed $PROFILE video publisher systemd unit" + + # Audio capture publishing and forwarded-audio playback use the same ffmpeg + # and ALSA contract on both profiles, so they stay as shared installer work. + install -D -o root -g root -m 0755 pi/bin/audio-only-publisher.sh /usr/local/bin/audio-only-publisher + install -m 0644 pi/systemd/audio-only-publisher.service /etc/systemd/system/audio-only-publisher.service + log "Installed audio-only publisher helper + systemd unit" + + install -D -o root -g root -m 0755 pi/bin/audio-forward-listener.sh /usr/local/bin/audio-forward-listener + install -m 0644 pi/systemd/audio-forward-listener.service /etc/systemd/system/audio-forward-listener.service + log "Installed audio-forward listener helper + systemd unit" +} + +enable_and_restart_units() { + systemctl daemon-reload + systemctl enable roverd.service + systemctl enable "$VIDEO_SERVICE_NAME" + systemctl enable audio-only-publisher.service + systemctl enable audio-forward-listener.service + + if [[ $CONFIG_EXISTS -eq 1 ]]; then + # An existing config means this host was already configured, so restart + # immediately to pick up the new binary, helpers, units, and media env. + systemctl restart roverd.service + systemctl restart "$VIDEO_SERVICE_NAME" + systemctl restart audio-only-publisher.service + systemctl restart audio-forward-listener.service + log "Restarted roverd + media publishers/listener" + else + # A freshly installed sample config usually still has placeholder names + # and URLs. Enabling without starting avoids connecting a half-configured + # rover to the control server by accident. + log "Skipped auto-start because config is the sample; edit $CONFIG_DEST then run: sudo systemctl restart roverd ${VIDEO_SERVICE_NAME%.service} audio-only-publisher audio-forward-listener" + fi +} diff --git a/pi/install_roverd.sh b/pi/install_roverd.sh index 13c6672e..321b15df 100755 --- a/pi/install_roverd.sh +++ b/pi/install_roverd.sh @@ -1,513 +1,48 @@ #!/usr/bin/env bash # -# Installer for the roverd agent on Raspberry Pi and Debian laptop rover hosts. +# Installer entry point for roverd hosts. +# +# The detailed work lives in pi/install/*.sh so platform-specific setup stays +# local to the profile that needs it. This file should read like the install +# order: choose a profile, validate inputs, install shared roverd pieces, +# install profile support, then enable the services. set -euo pipefail -PROFILE="pi" -BINARY_SRC="" -CONFIG_SRC="" -REPO_ROOT="$(pwd -P)" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" -usage() { - cat <<'USAGE' -Usage: sudo ./pi/install_roverd.sh [options] +# The helper scripts use repository-relative paths because those same paths are +# shown in docs and logs. Moving to the repository root once keeps every module +# simple and prevents each function from needing its own path resolver. +cd "$REPO_ROOT" -Options: - --profile Install profile: pi or debian-laptop (default: pi) - --debian-laptop Shortcut for --profile debian-laptop - -b, --binary Path to the roverd binary (default: dist/roverd) - -c, --config Source config to install if /etc/roverd.yaml is missing - (default: pi/roverd/roverd.sample.yaml) - -h, --help Show this help text +source pi/install/common.sh +source pi/install/profiles.sh +source pi/install/self_update.sh +source pi/install/pi_profile.sh +source pi/install/debian_laptop_profile.sh +source pi/install/media_env.sh +source pi/install/systemd.sh -The script must run from the repository root and as root (sudo). It will: - * create system users/groups if needed - * install /usr/local/bin/roverd and /etc/roverd.yaml - * install profile-specific video helpers and shared audio helpers - * install Pi-only Google Chrome TTS assets when using the pi profile - * install the fixed-command self-update helper used by admin-triggered updates - * enable roverd.service and media publisher/listener services -USAGE +main() { + parse_args "$@" + select_profile + require_root + validate_install_inputs + + install_roverd_binary + install_self_update_support + install_roverd_config + install_roverd_unit + + install_selected_profile + install_media_units + write_media_env_placeholder + install_audio_fifo + enable_and_restart_units + + log "Install complete for profile $PROFILE" } -while [[ $# -gt 0 ]]; do - case "$1" in - --profile) - PROFILE="${2:-}" - shift 2 - ;; - --debian-laptop) - PROFILE="debian-laptop" - shift - ;; - -b|--binary) - BINARY_SRC="${2:-}" - shift 2 - ;; - -c|--config) - CONFIG_SRC="${2:-}" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "Unknown option: $1" >&2 - usage - exit 1 - ;; - esac -done - -case "$PROFILE" in - pi) - BINARY_SRC="${BINARY_SRC:-dist/roverd}" - CONFIG_SRC="${CONFIG_SRC:-pi/roverd/roverd.sample.yaml}" - VIDEO_HELPER_SRC="pi/bin/video-publisher.sh" - VIDEO_HELPER_DEST="/usr/local/bin/video-publisher" - VIDEO_SERVICE_SRC="pi/systemd/video-publisher.service" - VIDEO_SERVICE_NAME="video-publisher.service" - ROVERD_GROUPS="dialout,gpio,video,render,audio" - ;; - debian-laptop) - BINARY_SRC="${BINARY_SRC:-dist/roverd-debian-laptop}" - CONFIG_SRC="${CONFIG_SRC:-pi/roverd/roverd.debian-laptop.sample.yaml}" - VIDEO_HELPER_SRC="pi/bin/debian-laptop-video-publisher.sh" - VIDEO_HELPER_DEST="/usr/local/bin/debian-laptop-video-publisher" - VIDEO_SERVICE_SRC="pi/systemd/debian-laptop-video-publisher.service" - VIDEO_SERVICE_NAME="debian-laptop-video-publisher.service" - ROVERD_GROUPS="dialout,video,render,audio" - ;; - *) - echo "Unknown profile: $PROFILE" >&2 - usage - exit 1 - ;; -esac - -if [[ "${EUID}" -ne 0 ]]; then - echo "Please run as root (sudo)" >&2 - exit 1 -fi - -if [[ ! -f "$BINARY_SRC" ]]; then - echo "Binary not found at $BINARY_SRC" >&2 - exit 1 -fi - -if [[ ! -f "$CONFIG_SRC" ]]; then - echo "Config source not found at $CONFIG_SRC" >&2 - exit 1 -fi - -ensure_user() { - local user="$1" - local groups="${2:-}" - local existing_groups="" - if ! id -u "$user" >/dev/null 2>&1; then - if [[ -n "$groups" ]]; then - local group - IFS=',' read -ra group_list <<< "$groups" - for group in "${group_list[@]}"; do - if getent group "$group" >/dev/null 2>&1; then - existing_groups="${existing_groups:+$existing_groups,}$group" - else - log "Skipping missing system group '$group' for $user" - fi - done - fi - if [[ -n "$existing_groups" ]]; then - useradd -r -s /usr/sbin/nologin -G "$existing_groups" "$user" - else - useradd -r -s /usr/sbin/nologin "$user" - fi - elif [[ -n "$groups" ]]; then - local group - IFS=',' read -ra group_list <<< "$groups" - for group in "${group_list[@]}"; do - if getent group "$group" >/dev/null 2>&1; then - existing_groups="${existing_groups:+$existing_groups,}$group" - else - log "Skipping missing system group '$group' for $user" - fi - done - if [[ -n "$existing_groups" ]]; then - usermod -a -G "$existing_groups" "$user" - fi - fi -} - -log() { - echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" -} - -if [[ "$PROFILE" == "pi" ]] && ! command -v rpicam-vid >/dev/null 2>&1 && ! command -v libcamera-vid >/dev/null 2>&1; then - log "WARNING: neither rpicam-vid nor libcamera-vid found in PATH; install libcamera-apps." -fi - -install_video_deps() { - if command -v ffmpeg >/dev/null 2>&1 && (command -v rpicam-vid >/dev/null 2>&1 || command -v libcamera-vid >/dev/null 2>&1); then - log "Video dependencies already installed; skipping apt install" - return - fi - log "Installing video dependencies (libcamera-apps, ffmpeg)..." - apt-get update - apt-get install -y --no-install-recommends libcamera-apps ffmpeg -} - -install_debian_laptop_deps() { - # The debian-laptop profile is intentionally Debian-specific, so using apt - # and systemd directly is simpler than pretending this installer is a - # cross-distro abstraction. v4l-utils is included for local camera inspection - # with tools such as v4l2-ctl while ffmpeg owns publishing. - if command -v ffmpeg >/dev/null 2>&1 \ - && command -v arecord >/dev/null 2>&1 \ - && command -v aplay >/dev/null 2>&1 \ - && command -v v4l2-ctl >/dev/null 2>&1; then - log "Debian laptop media dependencies already installed; skipping apt install" - return - fi - log "Installing Debian laptop media dependencies (ffmpeg, ALSA tools, V4L2 tools)..." - apt-get update - apt-get install -y --no-install-recommends ffmpeg alsa-utils v4l-utils ca-certificates -} - -find_boot_config() { - if [[ -f /boot/firmware/config.txt ]]; then - printf "/boot/firmware/config.txt" - return 0 - fi - if [[ -f /boot/config.txt ]]; then - printf "/boot/config.txt" - return 0 - fi - return 1 -} - -ensure_pwm_overlay() { - local boot_config - if ! boot_config="$(find_boot_config)"; then - log "WARNING: unable to locate /boot config.txt; please ensure dtoverlay=pwm-2chan is added manually for servo support" - return - fi - if grep -Eq '^\s*dtoverlay=pwm(-2chan)?' "$boot_config"; then - log "PWM overlay already present in $boot_config" - return - fi - local backup="${boot_config}.roverd.$(date +%Y%m%d%H%M%S).bak" - cp "$boot_config" "$backup" - { - echo "" - echo "# Added by roverd installer to expose PWM hardware for camera servo control on GPIO12/13 (leaves GPIO18/19 free for I2S)" - echo "dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4" - } >> "$boot_config" - log "Enabled dtoverlay=pwm-2chan on GPIO12/13 in $boot_config (backup at $backup). Reboot required for changes to apply." -} - -install_profile_dependencies() { - case "$PROFILE" in - pi) - install_video_deps - ensure_pwm_overlay - ;; - debian-laptop) - install_debian_laptop_deps - ;; - esac -} - -install_profile_audio_support() { - case "$PROFILE" in - pi) - install_audio_support - ;; - debian-laptop) - # Laptop audio uses the distro's normal ALSA "default" device by - # default. Avoid installing the Raspberry Pi AIY asound.conf because - # that would break ordinary laptop sound routing. - log "Skipping Pi AIY audio setup for debian-laptop profile" - ;; - esac -} - -ensure_user roverd "$ROVERD_GROUPS" -install -o roverd -g roverd -m 0755 "$BINARY_SRC" /usr/local/bin/roverd -log "Installed roverd binary for profile $PROFILE" - -install_self_update_support() { - local sudoers_file="/etc/sudoers.d/roverd-self-update" - local update_env="/etc/roverd-update.env" - local quoted_repo_root - - # The self-update helper must know which checkout should receive the git - # pull. Recording the repository root during the normal installer run keeps - # the runtime websocket command simple and prevents the rover from accepting - # a caller-controlled path. - printf -v quoted_repo_root '%q' "$REPO_ROOT" - install -D -o root -g root -m 0644 /dev/null "$update_env" - cat > "$update_env" < "$sudoers_file" <<'SUDOERS' -# Managed by pi/install_roverd.sh. -# Allow only the roverd service account to run the fixed self-update helper. -roverd ALL=(root) NOPASSWD: /usr/local/sbin/roverd-self-update -SUDOERS - chown root:root "$sudoers_file" - chmod 0440 "$sudoers_file" - if command -v visudo >/dev/null 2>&1; then - visudo -cf "$sudoers_file" >/dev/null - fi - log "Installed roverd self-update helper and sudoers rule" -} - -install_self_update_support - -CONFIG_DEST="/etc/roverd.yaml" -CONFIG_EXISTS=0 -if [[ -f "$CONFIG_DEST" ]]; then - CONFIG_EXISTS=1 - log "Existing $CONFIG_DEST found; leaving it in place" -else - install -D -o roverd -g roverd -m 0640 "$CONFIG_SRC" "$CONFIG_DEST" - log "Installed sample config to $CONFIG_DEST (edit before starting service)" -fi - -install -m 0644 pi/systemd/roverd.service /etc/systemd/system/roverd.service -log "Installed roverd systemd unit" - -install_profile_dependencies - -# Enable Google AIY v1 sound card, ALSA defaults, and TTS engines -install_audio_support() { - local boot_config - if ! boot_config="$(find_boot_config)"; then - log "WARNING: unable to locate /boot config.txt; please enable googlevoicehat-soundcard overlay manually" - else - # Ensure onboard audio is disabled (prevents card index flapping) - if grep -Eq '^\s*dtparam=audio=on\b' "$boot_config"; then - log "Disabling onboard audio (dtparam=audio=on -> off) in $boot_config" - sed -i 's/^\s*dtparam=audio=on\b/# roverd disabled onboard audio\ndtparam=audio=off/' "$boot_config" - fi - if ! grep -Eq '^\s*dtparam=audio=off\b' "$boot_config"; then - log "Adding dtparam=audio=off to $boot_config" - echo "dtparam=audio=off" >> "$boot_config" - fi - if ! grep -Eq '^\s*dtparam=i2s=on\b' "$boot_config"; then - log "Adding dtparam=i2s=on to $boot_config" - echo "dtparam=i2s=on" >> "$boot_config" - fi - if ! grep -Eq '^\s*dtoverlay=googlevoicehat-soundcard\b' "$boot_config"; then - local backup="${boot_config}.roverd.$(date +%Y%m%d%H%M%S).bak" - cp "$boot_config" "$backup" - { - echo "" - echo "# Added by roverd installer to enable Google AIY v1 sound card" - echo "dtoverlay=googlevoicehat-soundcard" - } >> "$boot_config" - log "Enabled googlevoicehat-soundcard overlay in $boot_config (backup at $backup). Reboot required." - else - log "googlevoicehat-soundcard overlay already present in $boot_config" - fi - fi - if [[ -f pi/asound.conf ]]; then - install -m 0644 pi/asound.conf /etc/asound.conf - log "Installed ALSA config to /etc/asound.conf" - alsa_reload_notice=1 - else - log "WARNING: pi/asound.conf missing; skipping ALSA config install" - fi - - if [[ "${alsa_reload_notice:-0}" -eq 1 ]]; then - log "ALSA config updated; reboot recommended for overlay + audio changes" - fi - - log "Installing TTS/audio packages (flite, espeak, Chrome TTS runtime deps)..." - # check for flite and espeak before installing, and then install them if either is missing - if command -v flite >/dev/null 2>&1 \ - && command -v espeak >/dev/null 2>&1 \ - && command -v python3 >/dev/null 2>&1 \ - && command -v curl >/dev/null 2>&1 \ - && command -v xz >/dev/null 2>&1 \ - && command -v unzip >/dev/null 2>&1 \ - && command -v aplay >/dev/null 2>&1 \ - && ldconfig -p 2>/dev/null | grep -q 'libc++\.so\.1' \ - && ldconfig -p 2>/dev/null | grep -q 'libc++abi\.so\.1'; then - log "Core TTS packages already installed; skipping apt install" - else - apt-get update - apt-get install -y --no-install-recommends flite espeak python3 curl xz-utils unzip alsa-utils libc++1 libc++abi1 \ - || apt-get install -y --no-install-recommends flite espeak python3 curl xz-utils unzip alsa-utils libc++1-14 libc++abi1-14 - fi - - install -D -o root -g root -m 0755 pi/bin/chromegtts-daemon.py /usr/local/bin/chromegtts-daemon - log "Installed chromegtts daemon" - - install_google_tts_assets -} - -install_google_tts_assets() { - local asset_dir="/opt/roverd/googletts" - local voice_dir="${asset_dir}/en-us-x-multi-r30" - local dist_url="https://storage.googleapis.com/chromeos-localmirror/distfiles/googletts-26.5.tar.xz" - local tmp_dir - local lib_member - - case "$(uname -m)" in - aarch64|arm64) - lib_member="libchrometts_arm64.so" - ;; - armv7l|armhf) - lib_member="libchrometts_armv7.so" - ;; - *) - log "WARNING: unsupported Chrome TTS architecture $(uname -m); skipping Google TTS assets" - return - ;; - esac - - if [[ -f "${asset_dir}/libchrometts.so" && -f "${voice_dir}/pipeline.pb" ]]; then - log "Google Chrome TTS assets already installed; skipping download" - return - fi - - tmp_dir="$(mktemp -d)" - log "Downloading Google Chrome TTS assets..." - curl -L -o "${tmp_dir}/googletts-26.5.tar.xz" "$dist_url" - tar -xf "${tmp_dir}/googletts-26.5.tar.xz" -C "$tmp_dir" en-us-x-multi.zvoice "$lib_member" - - install -d -o root -g root -m 0755 "$asset_dir" - install -o root -g root -m 0644 "${tmp_dir}/${lib_member}" "${asset_dir}/libchrometts.so" - rm -rf "$voice_dir" - install -d -o root -g root -m 0755 "$voice_dir" - unzip -q "${tmp_dir}/en-us-x-multi.zvoice" -d "$voice_dir" - chown -R root:root "$asset_dir" - find "$asset_dir" -type d -exec chmod 0755 {} + - find "$asset_dir" -type f -exec chmod 0644 {} + - rm -rf "$tmp_dir" - log "Installed Google Chrome TTS assets to $asset_dir" -} - -# Install the profile-specific video publisher. The audio publisher/listener are -# shared because both Pi and Debian laptop profiles use the same media.env audio -# contract and ffmpeg/ALSA command shape. -install -D -o root -g root -m 0755 "$VIDEO_HELPER_SRC" "$VIDEO_HELPER_DEST" -log "Installed $PROFILE video publisher helper" -install -m 0644 "$VIDEO_SERVICE_SRC" "/etc/systemd/system/$VIDEO_SERVICE_NAME" -log "Installed $PROFILE video publisher systemd unit" -# Install audio-only publisher assets -install -D -o root -g root -m 0755 pi/bin/audio-only-publisher.sh /usr/local/bin/audio-only-publisher -install -m 0644 pi/systemd/audio-only-publisher.service /etc/systemd/system/audio-only-publisher.service -log "Installed audio-only publisher helper + systemd unit" -# Install audio-forward listener assets -install -D -o root -g root -m 0755 pi/bin/audio-forward-listener.sh /usr/local/bin/audio-forward-listener -install -m 0644 pi/systemd/audio-forward-listener.service /etc/systemd/system/audio-forward-listener.service -log "Installed audio-forward listener helper + systemd unit" -install -d -o roverd -g roverd /var/lib/roverd -case "$PROFILE" in - pi) - cat > /var/lib/roverd/media.env <<'ENV' -# Managed by roverd; placeholder values will be overwritten at runtime. -ROVERD_VIDEO_ENABLE=1 -ROVERD_VIDEO_PUBLISHER=pi-libcamera -ROVERD_VIDEO_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316 -ROVERD_VIDEO_DEVICE= -ROVERD_VIDEO_INPUT_FORMAT= -ROVERD_VIDEO_WIDTH=640 -ROVERD_VIDEO_HEIGHT=480 -ROVERD_VIDEO_FPS=30 -ROVERD_VIDEO_BITRATE=2000000 -ROVERD_VIDEO_INVERT=1 -ROVERD_VIDEO_SENSOR_MODE=1296:972 -ROVERD_AUDIO_CAPTURE_ENABLE=0 -ROVERD_AUDIO_CAPTURE_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME-audio,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316 -ROVERD_AUDIO_CAPTURE_DEVICE=hw:0,0 -ROVERD_AUDIO_CAPTURE_SAMPLE_RATE=48000 -ROVERD_AUDIO_CAPTURE_CHANNELS=2 -ROVERD_AUDIO_CAPTURE_BITRATE=510000 -ROVERD_AUDIO_PLAYBACK_ENABLE=1 -ROVERD_AUDIO_PLAYBACK_FORWARD_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME-fwd,m=request&latency=10&mode=caller&transtype=live&pkt_size=1316 -ROVERD_AUDIO_PLAYBACK_DEVICE=forward -ROVERD_AUDIO_PLAYBACK_NORMALIZE=1 -ROVERD_AUDIO_PLAYBACK_NORMALIZE_FILTER=dynaudnorm=f=75:g=15:m=10:p=0.9,alimiter=limit=0.85:level=disabled -ENV - ;; - debian-laptop) - cat > /var/lib/roverd/media.env <<'ENV' -# Managed by roverd; placeholder values will be overwritten at runtime. -ROVERD_VIDEO_ENABLE=1 -ROVERD_VIDEO_PUBLISHER=debian-laptop-v4l2 -ROVERD_VIDEO_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316 -ROVERD_VIDEO_DEVICE=/dev/video0 -ROVERD_VIDEO_INPUT_FORMAT=mjpeg -ROVERD_VIDEO_WIDTH=640 -ROVERD_VIDEO_HEIGHT=480 -ROVERD_VIDEO_FPS=30 -ROVERD_VIDEO_BITRATE=2000000 -ROVERD_VIDEO_INVERT=0 -ROVERD_VIDEO_SENSOR_MODE= -ROVERD_AUDIO_CAPTURE_ENABLE=1 -ROVERD_AUDIO_CAPTURE_PUBLISH_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME-audio,m=publish&latency=10&mode=caller&transtype=live&pkt_size=1316 -ROVERD_AUDIO_CAPTURE_DEVICE=default -ROVERD_AUDIO_CAPTURE_SAMPLE_RATE=48000 -ROVERD_AUDIO_CAPTURE_CHANNELS=2 -ROVERD_AUDIO_CAPTURE_BITRATE=510000 -ROVERD_AUDIO_PLAYBACK_ENABLE=1 -ROVERD_AUDIO_PLAYBACK_FORWARD_URL=srt://192.168.0.86:9000?streamid=#!::r=CHANGE_ME-fwd,m=request&latency=10&mode=caller&transtype=live&pkt_size=1316 -ROVERD_AUDIO_PLAYBACK_DEVICE=default -ROVERD_AUDIO_PLAYBACK_NORMALIZE=1 -ROVERD_AUDIO_PLAYBACK_NORMALIZE_FILTER=dynaudnorm=f=75:g=15:m=10:p=0.9,alimiter=limit=0.85:level=disabled -ENV - ;; -esac -chown roverd:roverd /var/lib/roverd/media.env -chmod 0640 /var/lib/roverd/media.env -# Create persistent audio FIFO for capture -> publisher -FIFO_PATH="/var/lib/roverd/audio.pcm" -if [[ -p "$FIFO_PATH" ]]; then - chown roverd:audio "$FIFO_PATH" - chmod 0660 "$FIFO_PATH" -else - rm -f "$FIFO_PATH" - mkfifo "$FIFO_PATH" - chown roverd:audio "$FIFO_PATH" - chmod 0660 "$FIFO_PATH" -fi -if [[ "$PROFILE" == "pi" ]]; then - # Ensure ALSA config is in place for the Raspberry Pi Google Voice HAT - # rovermic/forward devices. Debian laptop installs should keep their normal - # distro sound configuration and use device: default in roverd.yaml. - install -m 0644 pi/asound.conf /etc/asound.conf - log "Installed ALSA config (/etc/asound.conf)" -fi - -install_profile_audio_support - -systemctl daemon-reload -systemctl enable roverd.service -systemctl enable "$VIDEO_SERVICE_NAME" -systemctl enable audio-only-publisher.service -systemctl enable audio-forward-listener.service -if [[ $CONFIG_EXISTS -eq 1 ]]; then - systemctl restart roverd.service - systemctl restart "$VIDEO_SERVICE_NAME" - systemctl restart audio-only-publisher.service - systemctl restart audio-forward-listener.service - log "Restarted roverd + media publishers/listener" -else - log "Skipped auto-start because config is the sample; edit $CONFIG_DEST then run: sudo systemctl restart roverd ${VIDEO_SERVICE_NAME%.service} audio-only-publisher audio-forward-listener" -fi - -log "Install complete for profile $PROFILE" +main "$@" diff --git a/pi/roverd/roverd.debian-laptop.sample.yaml b/pi/roverd/roverd.debian-laptop.sample.yaml index d4b2e750..060224e5 100644 --- a/pi/roverd/roverd.debian-laptop.sample.yaml +++ b/pi/roverd/roverd.debian-laptop.sample.yaml @@ -50,17 +50,27 @@ media: audioPlayback: enabled: true service: audio-forward-listener.service - device: default + # The Debian laptop installer creates a named "forward" ALSA device so + # browser-forwarded audio has its own ForwardMaster mixer control instead + # of sharing the TTS default output path. + device: forward normalize: true cameraServo: enabled: false audio: - ttsEnabled: false + # TTS does not need Pi-specific hardware. The laptop ALSA profile maps the + # normal default playback device through the TTSMaster softvol control, so + # the existing flite/espeak execution path can stay unchanged. + ttsEnabled: true + defaultEngine: flite horn: - enabled: false + # Horn audio is generated locally by roverd and plays through the named + # "horn" ALSA device by default, which the laptop installer wires to a + # separate HornMaster softvol control. + enabled: true headlight: enabled: false diff --git a/to-do.md b/to-do.md index ecbb890b..d71749d0 100644 --- a/to-do.md +++ b/to-do.md @@ -1,6 +1,9 @@ 1. improve spectator page, options on what to see and what not to see -2. fix private rover requests not working..... -3. fix this: +2. pagewide system for "why was i removed from a rover", instead of the link to spectator page thing +3. admin command for kicking people off of rovers. just a kick, nothing persistent +4. kick people off rover after 3 consecurtive bump-off attempts +5. make signaling more clear for idle skips and idle skip kicks +6. fix this: `Jun 18 15:14:18 roombaserver.local node[216731]: /home/daniel/MultiRoombaRover/server/src/services/roverManager/socketHandlers.js:92 Jun 18 15:14:18 roombaserver.local node[216731]: cb({ error: err.message }); Jun 18 15:14:18 roombaserver.local node[216731]: ^