diff --git a/dist/hornverifier b/dist/hornverifier index 92561340..2b3ee771 100755 Binary files a/dist/hornverifier and b/dist/hornverifier differ diff --git a/dist/roverd b/dist/roverd index 9312e05a..4032c1de 100755 Binary files a/dist/roverd and b/dist/roverd differ diff --git a/dist/roverd-debian-laptop b/dist/roverd-debian-laptop new file mode 100755 index 00000000..d8adec9a Binary files /dev/null and b/dist/roverd-debian-laptop differ diff --git a/dist/servoverifier b/dist/servoverifier index d7605719..2216711b 100755 Binary files a/dist/servoverifier and b/dist/servoverifier differ diff --git a/pi/bin/debian-laptop-video-publisher.sh b/pi/bin/debian-laptop-video-publisher.sh new file mode 100755 index 00000000..2e37c337 --- /dev/null +++ b/pi/bin/debian-laptop-video-publisher.sh @@ -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 diff --git a/pi/install_roverd.sh b/pi/install_roverd.sh index 27a71770..6a922aa6 100755 --- a/pi/install_roverd.sh +++ b/pi/install_roverd.sh @@ -1,11 +1,12 @@ #!/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 -BINARY_SRC="dist/roverd" -CONFIG_SRC="pi/roverd/roverd.sample.yaml" +PROFILE="pi" +BINARY_SRC="" +CONFIG_SRC="" REPO_ROOT="$(pwd -P)" usage() { @@ -13,6 +14,8 @@ 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: dist/roverd) -c, --config Source config to install if /etc/roverd.yaml is missing (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: * create system users/groups if needed * install /usr/local/bin/roverd and /etc/roverd.yaml - * install /usr/local/bin/video/audio helpers and systemd units - * install fixed-location Google Chrome TTS assets for roverd + * 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 @@ -30,6 +33,14 @@ USAGE 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 @@ -50,6 +61,32 @@ while [[ $# -gt 0 ]]; do 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 @@ -68,14 +105,37 @@ 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 - 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 useradd -r -s /usr/sbin/nologin "$user" fi 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 } @@ -83,7 +143,7 @@ log() { 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." fi @@ -97,6 +157,23 @@ install_video_deps() { 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" @@ -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." } -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 -log "Installed roverd binary" +log "Installed roverd binary for profile $PROFILE" install_self_update_support() { 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 log "Installed roverd systemd unit" -install_video_deps -ensure_pwm_overlay +install_profile_dependencies # Enable Google AIY v1 sound card, ALSA defaults, and TTS engines install_audio_support() { @@ -298,11 +400,13 @@ install_google_tts_assets() { log "Installed Google Chrome TTS assets to $asset_dir" } -# Install video publisher assets -install -D -o root -g root -m 0755 pi/bin/video-publisher.sh /usr/local/bin/video-publisher -log "Installed video-publisher helper" -install -m 0644 pi/systemd/video-publisher.service /etc/systemd/system/video-publisher.service -log "Installed video-publisher systemd unit" +# 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 @@ -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 log "Installed audio-forward listener helper + systemd unit" 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. ROVERD_VIDEO_ENABLE=1 ROVERD_VIDEO_PUBLISHER=pi-libcamera @@ -336,6 +442,34 @@ 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_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 @@ -349,25 +483,29 @@ else chown roverd:audio "$FIFO_PATH" chmod 0660 "$FIFO_PATH" fi -# Ensure ALSA config is in place for rovermic device -install -m 0644 pi/asound.conf /etc/asound.conf -log "Installed ALSA config (/etc/asound.conf)" +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_audio_support +install_profile_audio_support systemctl daemon-reload systemctl enable roverd.service -systemctl enable video-publisher.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-publisher.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-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 -log "Install complete" +log "Install complete for profile $PROFILE" diff --git a/pi/roverd/Makefile b/pi/roverd/Makefile index 4df4d7fb..d85f788a 100644 --- a/pi/roverd/Makefile +++ b/pi/roverd/Makefile @@ -3,18 +3,22 @@ GOOS ?= linux GOARCH ?= arm GOARM ?= 6 -.PHONY: build pi-build dummy clean +.PHONY: build pi-build debian-laptop dummy verifier-tools clean -build: - go build -o $(BIN_DIR)/roverd ./cmd/roverd +build: pi-build debian-laptop dummy -pi-build: - GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -trimpath -ldflags="-s -w" -o $(BIN_DIR)/roverd ./cmd/roverd +verifier-tools: 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 +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: GOOS=linux GOARCH=amd64 go build -tags dummy -o $(BIN_DIR)/roverd-dummy ./cmd/roverd 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 diff --git a/pi/roverd/brc.go b/pi/roverd/brc.go index 005ffdf2..df96319e 100644 --- a/pi/roverd/brc.go +++ b/pi/roverd/brc.go @@ -1,4 +1,4 @@ -//go:build !dummy +//go:build !dummy && !debian_laptop package roverd diff --git a/pi/roverd/brc_debian_laptop.go b/pi/roverd/brc_debian_laptop.go new file mode 100644 index 00000000..9e49e9ff --- /dev/null +++ b/pi/roverd/brc_debian_laptop.go @@ -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) {} diff --git a/pi/roverd/camera_servo.go b/pi/roverd/camera_servo.go index d1b2b067..83382ca5 100644 --- a/pi/roverd/camera_servo.go +++ b/pi/roverd/camera_servo.go @@ -1,4 +1,4 @@ -//go:build !dummy +//go:build !dummy && !debian_laptop package roverd diff --git a/pi/roverd/camera_servo_debian_laptop.go b/pi/roverd/camera_servo_debian_laptop.go new file mode 100644 index 00000000..16dc3f5f --- /dev/null +++ b/pi/roverd/camera_servo_debian_laptop.go @@ -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 +} diff --git a/pi/roverd/gpio_toggle.go b/pi/roverd/gpio_toggle.go index 52557cc3..43d1c9f7 100644 --- a/pi/roverd/gpio_toggle.go +++ b/pi/roverd/gpio_toggle.go @@ -1,4 +1,4 @@ -//go:build !dummy +//go:build !dummy && !debian_laptop package roverd diff --git a/pi/roverd/gpio_toggle_debian_laptop.go b/pi/roverd/gpio_toggle_debian_laptop.go new file mode 100644 index 00000000..71bfabed --- /dev/null +++ b/pi/roverd/gpio_toggle_debian_laptop.go @@ -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 +} diff --git a/pi/roverd/roverd.debian-laptop.sample.yaml b/pi/roverd/roverd.debian-laptop.sample.yaml new file mode 100644 index 00000000..57b2a3c4 --- /dev/null +++ b/pi/roverd/roverd.debian-laptop.sample.yaml @@ -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 diff --git a/pi/systemd/debian-laptop-video-publisher.service b/pi/systemd/debian-laptop-video-publisher.service new file mode 100644 index 00000000..6a9d61a1 --- /dev/null +++ b/pi/systemd/debian-laptop-video-publisher.service @@ -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