mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
debian-laptop
This commit is contained in:
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Executable
+131
@@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
set +H
|
||||||
|
|
||||||
|
ENV_FILE="${ROVERD_MEDIA_ENV_FILE:-/var/lib/roverd/media.env}"
|
||||||
|
|
||||||
|
if [[ ! -f "$ENV_FILE" ]]; then
|
||||||
|
echo "Environment file ${ENV_FILE} missing; cannot publish laptop video" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load roverd's generated media.env as data instead of sourcing it as shell.
|
||||||
|
# The SRT publish URL contains normal query-string characters like '&' and '#!',
|
||||||
|
# so evaluating the file would be both fragile and unnecessary.
|
||||||
|
load_env_file() {
|
||||||
|
local content=""
|
||||||
|
|
||||||
|
if [[ -r "$ENV_FILE" ]]; then
|
||||||
|
content="$(cat "$ENV_FILE")"
|
||||||
|
elif command -v sudo >/dev/null 2>&1; then
|
||||||
|
content="$(sudo -n cat "$ENV_FILE" 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$content" ]]; then
|
||||||
|
echo "Cannot read ${ENV_FILE} (permission denied). Run as a user that can read it, or allow sudo -n for cat." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local line key val
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
|
||||||
|
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||||
|
|
||||||
|
if [[ "$line" =~ ^[[:space:]]*export[[:space:]]+([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then
|
||||||
|
key="${BASH_REMATCH[1]}"
|
||||||
|
val="${BASH_REMATCH[2]}"
|
||||||
|
elif [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then
|
||||||
|
key="${BASH_REMATCH[1]}"
|
||||||
|
val="${BASH_REMATCH[2]}"
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
val="${val#${val%%[![:space:]]*}}"
|
||||||
|
val="${val%${val##*[![:space:]]}}"
|
||||||
|
if [[ "$val" =~ ^\".*\"$ ]]; then
|
||||||
|
val="${val:1:${#val}-2}"
|
||||||
|
elif [[ "$val" =~ ^\'.*\'$ ]]; then
|
||||||
|
val="${val:1:${#val}-2}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf -v "$key" '%s' "$val"
|
||||||
|
export "$key"
|
||||||
|
done <<< "$content"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_env_file
|
||||||
|
: "${ROVERD_VIDEO_ENABLE:?ROVERD_VIDEO_ENABLE not set in ${ENV_FILE}}"
|
||||||
|
: "${ROVERD_VIDEO_PUBLISH_URL:?ROVERD_VIDEO_PUBLISH_URL not set in ${ENV_FILE}}"
|
||||||
|
: "${ROVERD_VIDEO_DEVICE:?ROVERD_VIDEO_DEVICE not set in ${ENV_FILE}}"
|
||||||
|
: "${ROVERD_VIDEO_WIDTH:?ROVERD_VIDEO_WIDTH not set in ${ENV_FILE}}"
|
||||||
|
: "${ROVERD_VIDEO_HEIGHT:?ROVERD_VIDEO_HEIGHT not set in ${ENV_FILE}}"
|
||||||
|
: "${ROVERD_VIDEO_FPS:?ROVERD_VIDEO_FPS not set in ${ENV_FILE}}"
|
||||||
|
: "${ROVERD_VIDEO_BITRATE:?ROVERD_VIDEO_BITRATE not set in ${ENV_FILE}}"
|
||||||
|
: "${ROVERD_VIDEO_INVERT:?ROVERD_VIDEO_INVERT not set in ${ENV_FILE}}"
|
||||||
|
|
||||||
|
if [[ "${ROVERD_VIDEO_ENABLE}" -ne 1 ]]; then
|
||||||
|
echo "Laptop video publisher disabled by roverd media config; skipping" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${ROVERD_VIDEO_DEVICE}" ]]; then
|
||||||
|
echo "ROVERD_VIDEO_DEVICE is required for the debian-laptop V4L2 publisher" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${FFMPEG_BIN:-}" ]]; then
|
||||||
|
FFMPEG_BIN_PATH="$FFMPEG_BIN"
|
||||||
|
elif command -v ffmpeg >/dev/null 2>&1; then
|
||||||
|
FFMPEG_BIN_PATH="$(command -v ffmpeg)"
|
||||||
|
else
|
||||||
|
echo "ffmpeg not found; install it via apt install ffmpeg." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VIDEO_FILTER_ARGS=()
|
||||||
|
if [[ "${ROVERD_VIDEO_INVERT}" -ne 0 ]]; then
|
||||||
|
# The Pi publisher uses libcamera rotation. With a generic webcam, ffmpeg's
|
||||||
|
# transpose pair gives the same 180-degree correction without assuming a
|
||||||
|
# camera-specific driver feature.
|
||||||
|
VIDEO_FILTER_ARGS=(-vf "transpose=2,transpose=2")
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_pipeline() {
|
||||||
|
"${FFMPEG_BIN_PATH}" \
|
||||||
|
-hide_banner \
|
||||||
|
-loglevel warning \
|
||||||
|
-fflags nobuffer \
|
||||||
|
-flags low_delay \
|
||||||
|
-thread_queue_size 4096 \
|
||||||
|
-f v4l2 \
|
||||||
|
-framerate "${ROVERD_VIDEO_FPS}" \
|
||||||
|
-video_size "${ROVERD_VIDEO_WIDTH}x${ROVERD_VIDEO_HEIGHT}" \
|
||||||
|
-i "${ROVERD_VIDEO_DEVICE}" \
|
||||||
|
"${VIDEO_FILTER_ARGS[@]}" \
|
||||||
|
-an \
|
||||||
|
-c:v libx264 \
|
||||||
|
-preset veryfast \
|
||||||
|
-tune zerolatency \
|
||||||
|
-profile:v baseline \
|
||||||
|
-pix_fmt yuv420p \
|
||||||
|
-b:v "${ROVERD_VIDEO_BITRATE}" \
|
||||||
|
-maxrate "${ROVERD_VIDEO_BITRATE}" \
|
||||||
|
-bufsize "$((ROVERD_VIDEO_BITRATE / 2))" \
|
||||||
|
-g "${ROVERD_VIDEO_FPS}" \
|
||||||
|
-keyint_min "${ROVERD_VIDEO_FPS}" \
|
||||||
|
-sc_threshold 0 \
|
||||||
|
-flush_packets 1 \
|
||||||
|
-muxdelay 0 \
|
||||||
|
-muxpreload 0 \
|
||||||
|
-f mpegts \
|
||||||
|
"${ROVERD_VIDEO_PUBLISH_URL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if run_pipeline; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Debian laptop video publisher exited, restarting in 2s..." >&2
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
+164
-26
@@ -1,11 +1,12 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# Installer for the roverd agent on Raspberry Pi
|
# Installer for the roverd agent on Raspberry Pi and Debian laptop rover hosts.
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BINARY_SRC="dist/roverd"
|
PROFILE="pi"
|
||||||
CONFIG_SRC="pi/roverd/roverd.sample.yaml"
|
BINARY_SRC=""
|
||||||
|
CONFIG_SRC=""
|
||||||
REPO_ROOT="$(pwd -P)"
|
REPO_ROOT="$(pwd -P)"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
@@ -13,6 +14,8 @@ usage() {
|
|||||||
Usage: sudo ./pi/install_roverd.sh [options]
|
Usage: sudo ./pi/install_roverd.sh [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
--profile <name> Install profile: pi or debian-laptop (default: pi)
|
||||||
|
--debian-laptop Shortcut for --profile debian-laptop
|
||||||
-b, --binary <path> Path to the roverd binary (default: dist/roverd)
|
-b, --binary <path> Path to the roverd binary (default: dist/roverd)
|
||||||
-c, --config <path> Source config to install if /etc/roverd.yaml is missing
|
-c, --config <path> Source config to install if /etc/roverd.yaml is missing
|
||||||
(default: pi/roverd/roverd.sample.yaml)
|
(default: pi/roverd/roverd.sample.yaml)
|
||||||
@@ -21,8 +24,8 @@ Options:
|
|||||||
The script must run from the repository root and as root (sudo). It will:
|
The script must run from the repository root and as root (sudo). It will:
|
||||||
* create system users/groups if needed
|
* create system users/groups if needed
|
||||||
* install /usr/local/bin/roverd and /etc/roverd.yaml
|
* install /usr/local/bin/roverd and /etc/roverd.yaml
|
||||||
* install /usr/local/bin/video/audio helpers and systemd units
|
* install profile-specific video helpers and shared audio helpers
|
||||||
* install fixed-location Google Chrome TTS assets for roverd
|
* install Pi-only Google Chrome TTS assets when using the pi profile
|
||||||
* install the fixed-command self-update helper used by admin-triggered updates
|
* install the fixed-command self-update helper used by admin-triggered updates
|
||||||
* enable roverd.service and media publisher/listener services
|
* enable roverd.service and media publisher/listener services
|
||||||
USAGE
|
USAGE
|
||||||
@@ -30,6 +33,14 @@ USAGE
|
|||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
--profile)
|
||||||
|
PROFILE="${2:-}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--debian-laptop)
|
||||||
|
PROFILE="debian-laptop"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-b|--binary)
|
-b|--binary)
|
||||||
BINARY_SRC="${2:-}"
|
BINARY_SRC="${2:-}"
|
||||||
shift 2
|
shift 2
|
||||||
@@ -50,6 +61,32 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
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
|
if [[ "${EUID}" -ne 0 ]]; then
|
||||||
echo "Please run as root (sudo)" >&2
|
echo "Please run as root (sudo)" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -68,14 +105,37 @@ fi
|
|||||||
ensure_user() {
|
ensure_user() {
|
||||||
local user="$1"
|
local user="$1"
|
||||||
local groups="${2:-}"
|
local groups="${2:-}"
|
||||||
|
local existing_groups=""
|
||||||
if ! id -u "$user" >/dev/null 2>&1; then
|
if ! id -u "$user" >/dev/null 2>&1; then
|
||||||
if [[ -n "$groups" ]]; then
|
if [[ -n "$groups" ]]; then
|
||||||
useradd -r -s /usr/sbin/nologin -G "$groups" "$user"
|
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
|
else
|
||||||
useradd -r -s /usr/sbin/nologin "$user"
|
useradd -r -s /usr/sbin/nologin "$user"
|
||||||
fi
|
fi
|
||||||
elif [[ -n "$groups" ]]; then
|
elif [[ -n "$groups" ]]; then
|
||||||
usermod -a -G "$groups" "$user"
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +143,7 @@ log() {
|
|||||||
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"
|
echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"
|
||||||
}
|
}
|
||||||
|
|
||||||
if ! command -v rpicam-vid >/dev/null 2>&1 && ! command -v libcamera-vid >/dev/null 2>&1; then
|
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."
|
log "WARNING: neither rpicam-vid nor libcamera-vid found in PATH; install libcamera-apps."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -97,6 +157,23 @@ install_video_deps() {
|
|||||||
apt-get install -y --no-install-recommends libcamera-apps ffmpeg
|
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() {
|
find_boot_config() {
|
||||||
if [[ -f /boot/firmware/config.txt ]]; then
|
if [[ -f /boot/firmware/config.txt ]]; then
|
||||||
printf "/boot/firmware/config.txt"
|
printf "/boot/firmware/config.txt"
|
||||||
@@ -129,9 +206,35 @@ ensure_pwm_overlay() {
|
|||||||
log "Enabled dtoverlay=pwm-2chan on GPIO12/13 in $boot_config (backup at $backup). Reboot required for changes to apply."
|
log "Enabled dtoverlay=pwm-2chan on GPIO12/13 in $boot_config (backup at $backup). Reboot required for changes to apply."
|
||||||
}
|
}
|
||||||
|
|
||||||
ensure_user roverd "dialout,gpio,video,render,audio"
|
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
|
install -o roverd -g roverd -m 0755 "$BINARY_SRC" /usr/local/bin/roverd
|
||||||
log "Installed roverd binary"
|
log "Installed roverd binary for profile $PROFILE"
|
||||||
|
|
||||||
install_self_update_support() {
|
install_self_update_support() {
|
||||||
local sudoers_file="/etc/sudoers.d/roverd-self-update"
|
local sudoers_file="/etc/sudoers.d/roverd-self-update"
|
||||||
@@ -185,8 +288,7 @@ fi
|
|||||||
install -m 0644 pi/systemd/roverd.service /etc/systemd/system/roverd.service
|
install -m 0644 pi/systemd/roverd.service /etc/systemd/system/roverd.service
|
||||||
log "Installed roverd systemd unit"
|
log "Installed roverd systemd unit"
|
||||||
|
|
||||||
install_video_deps
|
install_profile_dependencies
|
||||||
ensure_pwm_overlay
|
|
||||||
|
|
||||||
# Enable Google AIY v1 sound card, ALSA defaults, and TTS engines
|
# Enable Google AIY v1 sound card, ALSA defaults, and TTS engines
|
||||||
install_audio_support() {
|
install_audio_support() {
|
||||||
@@ -298,11 +400,13 @@ install_google_tts_assets() {
|
|||||||
log "Installed Google Chrome TTS assets to $asset_dir"
|
log "Installed Google Chrome TTS assets to $asset_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install video publisher assets
|
# Install the profile-specific video publisher. The audio publisher/listener are
|
||||||
install -D -o root -g root -m 0755 pi/bin/video-publisher.sh /usr/local/bin/video-publisher
|
# shared because both Pi and Debian laptop profiles use the same media.env audio
|
||||||
log "Installed video-publisher helper"
|
# contract and ffmpeg/ALSA command shape.
|
||||||
install -m 0644 pi/systemd/video-publisher.service /etc/systemd/system/video-publisher.service
|
install -D -o root -g root -m 0755 "$VIDEO_HELPER_SRC" "$VIDEO_HELPER_DEST"
|
||||||
log "Installed video-publisher systemd unit"
|
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 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 -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
|
install -m 0644 pi/systemd/audio-only-publisher.service /etc/systemd/system/audio-only-publisher.service
|
||||||
@@ -312,7 +416,9 @@ install -D -o root -g root -m 0755 pi/bin/audio-forward-listener.sh /usr/local/b
|
|||||||
install -m 0644 pi/systemd/audio-forward-listener.service /etc/systemd/system/audio-forward-listener.service
|
install -m 0644 pi/systemd/audio-forward-listener.service /etc/systemd/system/audio-forward-listener.service
|
||||||
log "Installed audio-forward listener helper + systemd unit"
|
log "Installed audio-forward listener helper + systemd unit"
|
||||||
install -d -o roverd -g roverd /var/lib/roverd
|
install -d -o roverd -g roverd /var/lib/roverd
|
||||||
cat > /var/lib/roverd/media.env <<'ENV'
|
case "$PROFILE" in
|
||||||
|
pi)
|
||||||
|
cat > /var/lib/roverd/media.env <<'ENV'
|
||||||
# Managed by roverd; placeholder values will be overwritten at runtime.
|
# Managed by roverd; placeholder values will be overwritten at runtime.
|
||||||
ROVERD_VIDEO_ENABLE=1
|
ROVERD_VIDEO_ENABLE=1
|
||||||
ROVERD_VIDEO_PUBLISHER=pi-libcamera
|
ROVERD_VIDEO_PUBLISHER=pi-libcamera
|
||||||
@@ -336,6 +442,34 @@ ROVERD_AUDIO_PLAYBACK_DEVICE=forward
|
|||||||
ROVERD_AUDIO_PLAYBACK_NORMALIZE=1
|
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
|
ROVERD_AUDIO_PLAYBACK_NORMALIZE_FILTER=dynaudnorm=f=75:g=15:m=10:p=0.9,alimiter=limit=0.85:level=disabled
|
||||||
ENV
|
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_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
|
chown roverd:roverd /var/lib/roverd/media.env
|
||||||
chmod 0640 /var/lib/roverd/media.env
|
chmod 0640 /var/lib/roverd/media.env
|
||||||
# Create persistent audio FIFO for capture -> publisher
|
# Create persistent audio FIFO for capture -> publisher
|
||||||
@@ -349,25 +483,29 @@ else
|
|||||||
chown roverd:audio "$FIFO_PATH"
|
chown roverd:audio "$FIFO_PATH"
|
||||||
chmod 0660 "$FIFO_PATH"
|
chmod 0660 "$FIFO_PATH"
|
||||||
fi
|
fi
|
||||||
# Ensure ALSA config is in place for rovermic device
|
if [[ "$PROFILE" == "pi" ]]; then
|
||||||
install -m 0644 pi/asound.conf /etc/asound.conf
|
# Ensure ALSA config is in place for the Raspberry Pi Google Voice HAT
|
||||||
log "Installed ALSA config (/etc/asound.conf)"
|
# 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_audio_support
|
install_profile_audio_support
|
||||||
|
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable roverd.service
|
systemctl enable roverd.service
|
||||||
systemctl enable video-publisher.service
|
systemctl enable "$VIDEO_SERVICE_NAME"
|
||||||
systemctl enable audio-only-publisher.service
|
systemctl enable audio-only-publisher.service
|
||||||
systemctl enable audio-forward-listener.service
|
systemctl enable audio-forward-listener.service
|
||||||
if [[ $CONFIG_EXISTS -eq 1 ]]; then
|
if [[ $CONFIG_EXISTS -eq 1 ]]; then
|
||||||
systemctl restart roverd.service
|
systemctl restart roverd.service
|
||||||
systemctl restart video-publisher.service
|
systemctl restart "$VIDEO_SERVICE_NAME"
|
||||||
systemctl restart audio-only-publisher.service
|
systemctl restart audio-only-publisher.service
|
||||||
systemctl restart audio-forward-listener.service
|
systemctl restart audio-forward-listener.service
|
||||||
log "Restarted roverd + media publishers/listener"
|
log "Restarted roverd + media publishers/listener"
|
||||||
else
|
else
|
||||||
log "Skipped auto-start because config is the sample; edit $CONFIG_DEST then run: sudo systemctl restart roverd video-publisher audio-only-publisher audio-forward-listener"
|
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
|
fi
|
||||||
|
|
||||||
log "Install complete"
|
log "Install complete for profile $PROFILE"
|
||||||
|
|||||||
+10
-6
@@ -3,18 +3,22 @@ GOOS ?= linux
|
|||||||
GOARCH ?= arm
|
GOARCH ?= arm
|
||||||
GOARM ?= 6
|
GOARM ?= 6
|
||||||
|
|
||||||
.PHONY: build pi-build dummy clean
|
.PHONY: build pi-build debian-laptop dummy verifier-tools clean
|
||||||
|
|
||||||
build:
|
build: pi-build debian-laptop dummy
|
||||||
go build -o $(BIN_DIR)/roverd ./cmd/roverd
|
|
||||||
|
|
||||||
pi-build:
|
verifier-tools:
|
||||||
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -trimpath -ldflags="-s -w" -o $(BIN_DIR)/roverd ./cmd/roverd
|
|
||||||
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -trimpath -ldflags="-s -w" -o $(BIN_DIR)/servoverifier ./cmd/servoverifier
|
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -trimpath -ldflags="-s -w" -o $(BIN_DIR)/servoverifier ./cmd/servoverifier
|
||||||
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -trimpath -ldflags="-s -w" -o $(BIN_DIR)/hornverifier ./cmd/hornverifier
|
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -trimpath -ldflags="-s -w" -o $(BIN_DIR)/hornverifier ./cmd/hornverifier
|
||||||
|
|
||||||
|
pi-build: verifier-tools
|
||||||
|
GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -trimpath -ldflags="-s -w" -o $(BIN_DIR)/roverd ./cmd/roverd
|
||||||
|
|
||||||
|
debian-laptop:
|
||||||
|
GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -tags debian_laptop -o $(BIN_DIR)/roverd-debian-laptop ./cmd/roverd
|
||||||
|
|
||||||
dummy:
|
dummy:
|
||||||
GOOS=linux GOARCH=amd64 go build -tags dummy -o $(BIN_DIR)/roverd-dummy ./cmd/roverd
|
GOOS=linux GOARCH=amd64 go build -tags dummy -o $(BIN_DIR)/roverd-dummy ./cmd/roverd
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(BIN_DIR)/roverd $(BIN_DIR)/servoverifier $(BIN_DIR)/hornverifier
|
rm -f $(BIN_DIR)/roverd $(BIN_DIR)/roverd-debian-laptop $(BIN_DIR)/roverd-dummy $(BIN_DIR)/servoverifier $(BIN_DIR)/hornverifier
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
//go:build !dummy
|
//go:build !dummy && !debian_laptop
|
||||||
|
|
||||||
package roverd
|
package roverd
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
//go:build debian_laptop
|
||||||
|
|
||||||
|
package roverd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BRCPulser struct{}
|
||||||
|
|
||||||
|
func NewBRCPulser(_ BRCConfig, _ *log.Logger) (*BRCPulser, error) {
|
||||||
|
/*
|
||||||
|
The first Debian laptop profile keeps BRC disabled instead of pretending a
|
||||||
|
USB serial modem-control line has already been selected and tested. The
|
||||||
|
config can set brc.gpioPin: -1 to skip construction entirely; if someone
|
||||||
|
enables it, fail loudly so the rover does not silently miss wake pulses.
|
||||||
|
*/
|
||||||
|
return nil, fmt.Errorf("brc is not supported in the debian-laptop build yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BRCPulser) Close() {}
|
||||||
|
|
||||||
|
func (b *BRCPulser) Start(ctx context.Context) {}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !dummy
|
//go:build !dummy && !debian_laptop
|
||||||
|
|
||||||
package roverd
|
package roverd
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//go:build debian_laptop
|
||||||
|
|
||||||
|
package roverd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CameraServo struct{}
|
||||||
|
|
||||||
|
func NewCameraServo(_ CameraServoConfig, _ *log.Logger) (*CameraServo, error) {
|
||||||
|
/*
|
||||||
|
The Debian laptop profile starts with the laptop's built-in webcam and no
|
||||||
|
Pi PWM servo. If a laptop rover eventually grows an external servo board,
|
||||||
|
it should get its own implementation instead of reusing Raspberry Pi GPIO
|
||||||
|
assumptions.
|
||||||
|
*/
|
||||||
|
return nil, fmt.Errorf("camera servo not supported in the debian-laptop build")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CameraServo) Close() {}
|
||||||
|
|
||||||
|
func (c *CameraServo) SetAngle(angle float64) error {
|
||||||
|
return fmt.Errorf("camera servo disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CameraServo) Nudge(delta float64) error {
|
||||||
|
return fmt.Errorf("camera servo disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CameraServo) SetPulseWidth(micros int) error {
|
||||||
|
return fmt.Errorf("camera servo disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CameraServo) CurrentAngle() float64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !dummy
|
//go:build !dummy && !debian_laptop
|
||||||
|
|
||||||
package roverd
|
package roverd
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//go:build debian_laptop
|
||||||
|
|
||||||
|
package roverd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GPIOToggle struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGPIOToggle(name string, _ GPIOToggleConfig, _ *log.Logger) (*GPIOToggle, error) {
|
||||||
|
/*
|
||||||
|
A Debian laptop has no Raspberry Pi GPIO character-device contract for
|
||||||
|
headlights or lasers. Returning an error when enabled makes bad laptop
|
||||||
|
configs fail during startup instead of advertising controls that cannot
|
||||||
|
change any hardware.
|
||||||
|
*/
|
||||||
|
return nil, fmt.Errorf("%s not supported in the debian-laptop build", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GPIOToggle) Close() {}
|
||||||
|
|
||||||
|
func (g *GPIOToggle) HandleAction(action string) error {
|
||||||
|
return fmt.Errorf("%s not supported in the debian-laptop build", g.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GPIOToggle) On() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# Sample configuration for roverd on a Debian laptop rover.
|
||||||
|
name: laptop-rover
|
||||||
|
description: "Debian laptop rover using USB serial, webcam, mic, and speakers."
|
||||||
|
color: "#4DB6AC"
|
||||||
|
serverUrl: ws://control-server.local:8080/rover
|
||||||
|
|
||||||
|
serial:
|
||||||
|
device: /dev/ttyUSB0
|
||||||
|
baud: 115200
|
||||||
|
|
||||||
|
# BRC is disabled in the first Debian laptop profile because USB serial
|
||||||
|
# RTS/DTR wake-pulse support needs adapter-specific testing before it should be
|
||||||
|
# trusted to keep a Roomba awake.
|
||||||
|
brc:
|
||||||
|
gpioPin: -1
|
||||||
|
|
||||||
|
battery:
|
||||||
|
full: 2068
|
||||||
|
warn: 1700
|
||||||
|
urgent: 1650
|
||||||
|
|
||||||
|
maxWheelSpeed: 350
|
||||||
|
|
||||||
|
media:
|
||||||
|
publishPort: 9000
|
||||||
|
manage: true
|
||||||
|
healthUrl: ""
|
||||||
|
healthInterval: 30s
|
||||||
|
video:
|
||||||
|
enabled: true
|
||||||
|
service: debian-laptop-video-publisher.service
|
||||||
|
publisher: debian-laptop-v4l2
|
||||||
|
device: /dev/video0
|
||||||
|
width: 640
|
||||||
|
height: 480
|
||||||
|
fps: 30
|
||||||
|
bitrate: 2000000
|
||||||
|
inverted: false
|
||||||
|
audioCapture:
|
||||||
|
enabled: true
|
||||||
|
service: audio-only-publisher.service
|
||||||
|
device: default
|
||||||
|
sampleRate: 48000
|
||||||
|
channels: 2
|
||||||
|
bitrate: 510000
|
||||||
|
audioPlayback:
|
||||||
|
enabled: true
|
||||||
|
service: audio-forward-listener.service
|
||||||
|
device: default
|
||||||
|
normalize: true
|
||||||
|
|
||||||
|
cameraServo:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
audio:
|
||||||
|
ttsEnabled: false
|
||||||
|
|
||||||
|
horn:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
headlight:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
laser:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
autoSideBrush:
|
||||||
|
enabled: true
|
||||||
|
speed: 20
|
||||||
|
|
||||||
|
private:
|
||||||
|
enabled: false
|
||||||
|
safety:
|
||||||
|
speedLimitEnabled: false
|
||||||
|
speedLimitMaxWheelSpeed: 250
|
||||||
|
hardOvercurrentEnabled: false
|
||||||
|
overcurrentStopMs: 300
|
||||||
|
hardBumpEnabled: false
|
||||||
|
bumpBackoffSpeed: 250
|
||||||
|
bumpBackoffMs: 350
|
||||||
|
cliffEnabled: false
|
||||||
|
cliffBackoffSpeed: 250
|
||||||
|
cliffBackoffMs: 500
|
||||||
|
triggerCooldownMs: 800
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Rover Debian Laptop Video Publisher (V4L2 -> SRT)
|
||||||
|
After=network-online.target roverd.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=roverd
|
||||||
|
Group=roverd
|
||||||
|
WorkingDirectory=/var/lib/roverd
|
||||||
|
EnvironmentFile=/var/lib/roverd/media.env
|
||||||
|
ExecStart=/usr/local/bin/debian-laptop-video-publisher
|
||||||
|
Restart=always
|
||||||
|
RestartSec=2
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Reference in New Issue
Block a user