From e6c493121065f68458f2b522586825b1aa6b3411 Mon Sep 17 00:00:00 2001 From: legop3 <46182676+legop3@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:01:13 -0400 Subject: [PATCH 1/9] Make Debian laptop audio setup appliance-like --- pi/install/debian_laptop_profile.sh | 68 +++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/pi/install/debian_laptop_profile.sh b/pi/install/debian_laptop_profile.sh index 3276bd00..09d56857 100644 --- a/pi/install/debian_laptop_profile.sh +++ b/pi/install/debian_laptop_profile.sh @@ -1,23 +1,61 @@ #!/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. + # The laptop rover is a dedicated appliance, not a normal desktop/laptop audio + # install. Keep this package set intentionally close to the Pi profile so the + # same roverd TTS/playback/capture code paths are available on both targets. 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" + && 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 \ + && ldconfig -p 2>/dev/null | grep -q 'libc++\.so\.1' \ + && ldconfig -p 2>/dev/null | grep -q 'libc++abi\.so\.1'; then + log "Debian laptop media/audio/TTS dependencies already installed; skipping apt install" return fi - log "Installing Debian laptop dependencies (ffmpeg, ALSA tools, V4L2 tools, flite, espeak)..." + log "Installing Debian laptop rover dependencies (ffmpeg, ALSA tools, V4L2 tools, flite/espeak, Chrome TTS runtime deps)..." apt-get update - apt-get install -y --no-install-recommends ffmpeg alsa-utils v4l-utils ca-certificates flite espeak + apt-get install -y --no-install-recommends \ + ffmpeg alsa-utils v4l-utils ca-certificates flite espeak python3 curl xz-utils unzip libasound2-plugins libc++1 libc++abi1 \ + || apt-get install -y --no-install-recommends \ + ffmpeg alsa-utils v4l-utils ca-certificates flite espeak python3 curl xz-utils unzip libasound2-plugins libc++1-14 libc++abi1-14 +} + +disable_debian_laptop_desktop_audio_stack() { + # This profile is for a dedicated rover laptop. PipeWire/PulseAudio are good + # desktop defaults, but they can grab the hardware device and make the rover's + # root/systemd ALSA services fail or route through a moving per-user graph. + # Mask them globally and kill already-running instances so ALSA owns the box, + # which is the closest behavior to the Pi rover appliance setup. + log "Disabling desktop audio daemons for dedicated laptop rover audio" + + local -a user_units=( + pipewire.service + pipewire.socket + pipewire-pulse.service + pipewire-pulse.socket + wireplumber.service + pulseaudio.service + pulseaudio.socket + ) + + if command -v systemctl >/dev/null 2>&1; then + systemctl --global disable "${user_units[@]}" >/dev/null 2>&1 || true + systemctl --global mask "${user_units[@]}" >/dev/null 2>&1 || true + fi + + pkill -x pipewire >/dev/null 2>&1 || true + pkill -x pipewire-pulse >/dev/null 2>&1 || true + pkill -x wireplumber >/dev/null 2>&1 || true + pkill -x pulseaudio >/dev/null 2>&1 || true } install_debian_laptop_audio_support() { @@ -26,17 +64,19 @@ install_debian_laptop_audio_support() { 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" + log "Installed dedicated Debian laptop ALSA config to /etc/asound.conf" + + 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 + + log "ALSA config updated; reboot recommended before testing laptop rover audio" } install_debian_laptop_profile() { install_debian_laptop_deps + disable_debian_laptop_desktop_audio_stack install_debian_laptop_audio_support } From 11340bf3f6b1bf6240e10a48b324ea7d31d2a4a0 Mon Sep 17 00:00:00 2001 From: legop3 <46182676+legop3@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:01:26 -0400 Subject: [PATCH 2/9] Make laptop ALSA routing match Pi rover --- pi/asound.debian-laptop.conf | 132 +++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 61 deletions(-) diff --git a/pi/asound.debian-laptop.conf b/pi/asound.debian-laptop.conf index 3795ff1e..9144dde1 100644 --- a/pi/asound.debian-laptop.conf +++ b/pi/asound.debian-laptop.conf @@ -1,88 +1,98 @@ -# ALSA routing for the Debian laptop rover profile. +# Dedicated 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. +# This intentionally mirrors pi/asound.conf as closely as a normal PC can: +# one fixed hardware card, one dmix playback engine, separate softvol controls +# for TTS/horn/forwarded audio, and a raw capture alias for the rover mic. +# +# This is NOT meant to preserve normal desktop audio behavior. The laptop rover +# installer disables PipeWire/PulseAudio so roverd owns the audio hardware like +# the Raspberry Pi rover does. If the laptop's real speaker/mic card is not ALSA +# card 0, change the hw:0,0/card 0 references below to the card shown by: +# aplay -l +# arecord -l -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" +# Mix multiple playback clients in software with a fixed low-cost format. +pcm.dmixer { + type dmix + ipc_key 1024 + ipc_perm 0666 + slave { + pcm "hw:0,0" + format S16_LE + rate 16000 + channels 1 + period_time 0 + period_size 1024 + buffer_size 4096 + } } +# TTS volume control (used by default playback path). pcm.tts_softvol { - type softvol - slave.pcm "roverd_playback" - control { - name "TTSMaster" - card 0 - } - min_dB -60.0 - max_dB 12.0 + type softvol + slave.pcm "dmixer" + control { + name "TTSMaster" + card 0 + } + min_dB -60.0 + max_dB 12.0 } +# Horn volume control. pcm.horn_softvol { - type softvol - slave.pcm "roverd_playback" - control { - name "HornMaster" - card 0 - } - min_dB -60.0 - max_dB 12.0 + type softvol + slave.pcm "dmixer" + control { + name "HornMaster" + card 0 + } + min_dB -60.0 + max_dB 12.0 } +# Forwarded audio volume control. pcm.forward_softvol { - type softvol - slave.pcm "roverd_playback" - control { - name "ForwardMaster" - card 0 - } - min_dB -60.0 - max_dB 12.0 + type softvol + slave.pcm "dmixer" + control { + name "ForwardMaster" + card 0 + } + min_dB -60.0 + max_dB 12.0 } +# Per-source playback PCMs. pcm.tts { - type plug - slave.pcm "tts_softvol" + type plug + slave.pcm "tts_softvol" } pcm.horn { - type plug - slave.pcm "horn_softvol" + type plug + slave.pcm "horn_softvol" } pcm.forward { - type plug - slave.pcm "forward_softvol" + type plug + slave.pcm "forward_softvol" } -pcm.!default { - type asym +# Capture alias used by laptop rover config defaults. +pcm.rovermic { + type plug + slave.pcm "hw:0,0" +} - # 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" +# Defaults: TTS direct playback + raw capture on the dedicated laptop sound card. +pcm.!default { + type asym + playback.pcm "tts" + capture.pcm "rovermic" } ctl.!default { - type hw - card 0 + type hw + card 0 } From d349df243208f677612222fbbc5fb7ae65364c8e Mon Sep 17 00:00:00 2001 From: legop3 <46182676+legop3@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:01:51 -0400 Subject: [PATCH 3/9] Share Google TTS asset installer with laptop profile --- pi/install/pi_profile.sh | 69 ++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/pi/install/pi_profile.sh b/pi/install/pi_profile.sh index 599f114c..526d8d53 100644 --- a/pi/install/pi_profile.sh +++ b/pi/install/pi_profile.sh @@ -113,20 +113,8 @@ install_google_tts_assets() { 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 + local lib_member="" + local member if [[ -f "${asset_dir}/libchrometts.so" && -f "${voice_dir}/pipeline.pb" ]]; then log "Google Chrome TTS assets already installed; skipping download" @@ -135,8 +123,55 @@ install_google_tts_assets() { 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" + if ! curl -L -o "${tmp_dir}/googletts-26.5.tar.xz" "$dist_url"; then + rm -rf "$tmp_dir" + log "WARNING: failed to download Google Chrome TTS assets; chromegtts will be unavailable" + return + fi + + # The ChromeOS tarball has had architecture-specific library names. The Pi + # profile historically hard-coded ARM names; the laptop profile should be able + # to use the same installer when a matching x86_64 library exists, and should + # fail soft when it does not. + local -a candidate_libs=() + case "$(uname -m)" in + aarch64|arm64) + candidate_libs=(libchrometts_arm64.so libchrometts.so) + ;; + armv7l|armhf) + candidate_libs=(libchrometts_armv7.so libchrometts.so) + ;; + x86_64|amd64) + candidate_libs=(libchrometts_x86_64.so libchrometts_amd64.so libchrometts_x64.so libchrometts.so) + ;; + i386|i686) + candidate_libs=(libchrometts_x86.so libchrometts_i386.so libchrometts.so) + ;; + *) + log "WARNING: unsupported Chrome TTS architecture $(uname -m); skipping Google TTS assets" + rm -rf "$tmp_dir" + return + ;; + esac + + for member in "${candidate_libs[@]}"; do + if tar -tf "${tmp_dir}/googletts-26.5.tar.xz" "$member" >/dev/null 2>&1; then + lib_member="$member" + break + fi + done + + if [[ -z "$lib_member" ]]; then + log "WARNING: no libchrometts library matching $(uname -m) found in Google TTS archive; chromegtts will be unavailable" + rm -rf "$tmp_dir" + return + fi + + if ! tar -xf "${tmp_dir}/googletts-26.5.tar.xz" -C "$tmp_dir" en-us-x-multi.zvoice "$lib_member"; then + rm -rf "$tmp_dir" + log "WARNING: failed to unpack Google Chrome TTS assets; chromegtts will be unavailable" + return + fi 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" @@ -147,7 +182,7 @@ install_google_tts_assets() { 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" + log "Installed Google Chrome TTS assets to $asset_dir using $lib_member" } install_pi_profile() { From 2beb1498fa22837b1ecee67172bd6e9a2ea7983e Mon Sep 17 00:00:00 2001 From: legop3 <46182676+legop3@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:14:33 -0400 Subject: [PATCH 4/9] Preload compiler runtimes for Chrome TTS --- pi/bin/chromegtts-daemon.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/pi/bin/chromegtts-daemon.py b/pi/bin/chromegtts-daemon.py index 2eb7bd1f..1b842032 100644 --- a/pi/bin/chromegtts-daemon.py +++ b/pi/bin/chromegtts-daemon.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import ctypes +import ctypes.util import json import os import struct @@ -33,6 +34,40 @@ MAX_PITCH = 2.0 MIN_SPEED = 0.5 MAX_SPEED = 2.0 +_runtime_handles = [] + + +def load_shared_library(path): + mode = ctypes.RTLD_GLOBAL | getattr(os, "RTLD_NOW", 0) + return ctypes.CDLL(path, mode=mode) + + +def preload_runtime_libraries(): + # Some ChromeOS libchrometts builds reference compiler helper symbols such + # as __udivmodti4 without declaring the runtime library as an ELF dependency. + # Loading common compiler runtimes globally first makes those symbols visible + # before ctypes loads /opt/roverd/googletts/libchrometts.so. + candidates = [ + "gcc_s", + "atomic", + "stdc++", + "c++", + "c++abi", + ] + for name in candidates: + lib = ctypes.util.find_library(name) + if not lib: + continue + try: + _runtime_handles.append(load_shared_library(lib)) + except OSError: + pass + + +# Load the runtime helpers at module import time so daemon readiness fails only +# when libchrometts itself is still unusable after the best-effort preloads. +preload_runtime_libraries() + def varint(value): out = bytearray() @@ -64,7 +99,7 @@ def build_speaker(name, gender): class ChromeTTS: def __init__(self): - self.lib = ctypes.CDLL(LIB_PATH) + self.lib = load_shared_library(LIB_PATH) self.lib.GoogleTtsInit.argtypes = [ctypes.c_char_p, ctypes.c_char_p] self.lib.GoogleTtsInit.restype = ctypes.c_bool self.lib.GoogleTtsInitBuffered.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int] From f4683cd47deeb2538fd60e270b4cd458dd8b11b7 Mon Sep 17 00:00:00 2001 From: legop3 <46182676+legop3@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:14:45 -0400 Subject: [PATCH 5/9] Install GCC runtime for laptop Chrome TTS --- pi/install/debian_laptop_profile.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pi/install/debian_laptop_profile.sh b/pi/install/debian_laptop_profile.sh index 09d56857..439fe2a1 100644 --- a/pi/install/debian_laptop_profile.sh +++ b/pi/install/debian_laptop_profile.sh @@ -15,6 +15,8 @@ install_debian_laptop_deps() { && command -v curl >/dev/null 2>&1 \ && command -v xz >/dev/null 2>&1 \ && command -v unzip >/dev/null 2>&1 \ + && ldconfig -p 2>/dev/null | grep -q 'libgcc_s\.so\.1' \ + && ldconfig -p 2>/dev/null | grep -q 'libstdc\+\+\.so\.6' \ && ldconfig -p 2>/dev/null | grep -q 'libc++\.so\.1' \ && ldconfig -p 2>/dev/null | grep -q 'libc++abi\.so\.1'; then log "Debian laptop media/audio/TTS dependencies already installed; skipping apt install" @@ -24,9 +26,9 @@ install_debian_laptop_deps() { log "Installing Debian laptop rover dependencies (ffmpeg, ALSA tools, V4L2 tools, flite/espeak, Chrome TTS runtime deps)..." apt-get update apt-get install -y --no-install-recommends \ - ffmpeg alsa-utils v4l-utils ca-certificates flite espeak python3 curl xz-utils unzip libasound2-plugins libc++1 libc++abi1 \ + ffmpeg alsa-utils v4l-utils ca-certificates flite espeak python3 curl xz-utils unzip libasound2-plugins libgcc-s1 libstdc++6 libc++1 libc++abi1 \ || apt-get install -y --no-install-recommends \ - ffmpeg alsa-utils v4l-utils ca-certificates flite espeak python3 curl xz-utils unzip libasound2-plugins libc++1-14 libc++abi1-14 + ffmpeg alsa-utils v4l-utils ca-certificates flite espeak python3 curl xz-utils unzip libasound2-plugins libgcc-s1 libstdc++6 libc++1-14 libc++abi1-14 } disable_debian_laptop_desktop_audio_stack() { From f7514e71cc928c7a493c255927de112cf062474e Mon Sep 17 00:00:00 2001 From: legop3 <46182676+legop3@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:22:47 -0400 Subject: [PATCH 6/9] Restore Pi Chrome TTS daemon unchanged --- pi/bin/chromegtts-daemon.py | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/pi/bin/chromegtts-daemon.py b/pi/bin/chromegtts-daemon.py index 1b842032..2eb7bd1f 100644 --- a/pi/bin/chromegtts-daemon.py +++ b/pi/bin/chromegtts-daemon.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import ctypes -import ctypes.util import json import os import struct @@ -34,40 +33,6 @@ MAX_PITCH = 2.0 MIN_SPEED = 0.5 MAX_SPEED = 2.0 -_runtime_handles = [] - - -def load_shared_library(path): - mode = ctypes.RTLD_GLOBAL | getattr(os, "RTLD_NOW", 0) - return ctypes.CDLL(path, mode=mode) - - -def preload_runtime_libraries(): - # Some ChromeOS libchrometts builds reference compiler helper symbols such - # as __udivmodti4 without declaring the runtime library as an ELF dependency. - # Loading common compiler runtimes globally first makes those symbols visible - # before ctypes loads /opt/roverd/googletts/libchrometts.so. - candidates = [ - "gcc_s", - "atomic", - "stdc++", - "c++", - "c++abi", - ] - for name in candidates: - lib = ctypes.util.find_library(name) - if not lib: - continue - try: - _runtime_handles.append(load_shared_library(lib)) - except OSError: - pass - - -# Load the runtime helpers at module import time so daemon readiness fails only -# when libchrometts itself is still unusable after the best-effort preloads. -preload_runtime_libraries() - def varint(value): out = bytearray() @@ -99,7 +64,7 @@ def build_speaker(name, gender): class ChromeTTS: def __init__(self): - self.lib = load_shared_library(LIB_PATH) + self.lib = ctypes.CDLL(LIB_PATH) self.lib.GoogleTtsInit.argtypes = [ctypes.c_char_p, ctypes.c_char_p] self.lib.GoogleTtsInit.restype = ctypes.c_bool self.lib.GoogleTtsInitBuffered.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int] From f31b21559c55e991fd75c3b78830b07e4b2e3d5a Mon Sep 17 00:00:00 2001 From: legop3 <46182676+legop3@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:23:06 -0400 Subject: [PATCH 7/9] Add laptop-only Chrome TTS daemon --- pi/bin/chromegtts-daemon-laptop.py | 202 +++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 pi/bin/chromegtts-daemon-laptop.py diff --git a/pi/bin/chromegtts-daemon-laptop.py b/pi/bin/chromegtts-daemon-laptop.py new file mode 100644 index 00000000..46fad6f6 --- /dev/null +++ b/pi/bin/chromegtts-daemon-laptop.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +import ctypes +import ctypes.util +import json +import os +import struct +import subprocess +import sys + + +ASSET_ROOT = "/opt/roverd/googletts" +LIB_PATH = os.path.join(ASSET_ROOT, "libchrometts.so") +VOICE_DIR = os.path.join(ASSET_ROOT, "en-us-x-multi-r30") +PIPELINE = "pipeline.pb" +PLAYBACK_DEVICE = "tts" +SAMPLE_RATE = "24000" +MAX_TEXT_CHARS = 512 + +VOICES = { + "sfg": "female", + "iob": "female", + "iog": "female", + "iol": "male", + "iom": "male", + "tpc": "female", + "tpd": "male", + "tpf": "female", +} +DEFAULT_VOICE = "tpf" +DEFAULT_PITCH = 1.0 +DEFAULT_SPEED = 1.0 +MIN_PITCH = 0.5 +MAX_PITCH = 2.0 +MIN_SPEED = 0.5 +MAX_SPEED = 2.0 + +_runtime_handles = [] + + +def load_shared_library(path): + mode = ctypes.RTLD_GLOBAL | getattr(os, "RTLD_NOW", 0) + return ctypes.CDLL(path, mode=mode) + + +def preload_runtime_libraries(): + # Laptop-only workaround: some ChromeOS libchrometts builds reference + # compiler helper symbols such as __udivmodti4 without declaring the runtime + # library as an ELF dependency. Loading common compiler runtimes globally + # first makes those symbols visible before ctypes loads libchrometts.so. + for name in ("gcc_s", "atomic", "stdc++", "c++", "c++abi"): + lib = ctypes.util.find_library(name) + if not lib: + continue + try: + _runtime_handles.append(load_shared_library(lib)) + except OSError: + pass + + +preload_runtime_libraries() + + +def varint(value): + out = bytearray() + while value >= 0x80: + out.append((value & 0x7F) | 0x80) + value >>= 7 + out.append(value) + return bytes(out) + + +def field_bytes(number, payload): + return varint((number << 3) | 2) + varint(len(payload)) + payload + + +def field_float(number, value): + return varint((number << 3) | 5) + struct.pack(" 0: + frames = int(frames_written.value) + if frames > 0: + player.stdin.write(ctypes.string_at(self.buffer, frames * ctypes.sizeof(ctypes.c_float))) + player.stdin.close() + rc = player.wait() + if rc != 0: + raise RuntimeError(f"aplay exited with {rc}") + finally: + if player.poll() is None: + player.kill() + player.wait() + + def shutdown(self): + self.lib.GoogleTtsShutdown() + + +def respond(payload): + sys.stdout.write(json.dumps(payload, separators=(",", ":")) + "\n") + sys.stdout.flush() + + +def clamp_float(value, minimum, maximum, fallback): + try: + value = float(value) + except (TypeError, ValueError): + return fallback + if value <= 0: + return fallback + if value < minimum: + return minimum + if value > maximum: + return maximum + return value + + +def main(): + try: + tts = ChromeTTS() + except Exception as exc: + respond({"ok": False, "error": str(exc)}) + return 1 + + respond({"ok": True, "ready": True}) + try: + for line in sys.stdin: + line = line.strip() + if not line: + continue + try: + request = json.loads(line) + tts.speak_to_aplay( + str(request.get("text") or ""), + str(request.get("voice") or DEFAULT_VOICE), + request.get("pitch", DEFAULT_PITCH), + request.get("speed", DEFAULT_SPEED), + ) + respond({"ok": True}) + except Exception as exc: + respond({"ok": False, "error": str(exc)}) + finally: + tts.shutdown() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 77de628c0beb5250ad7f2d6bf478c4ce93bd4814 Mon Sep 17 00:00:00 2001 From: legop3 <46182676+legop3@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:23:29 -0400 Subject: [PATCH 8/9] Restore Pi Google TTS asset behavior --- pi/install/pi_profile.sh | 69 ++++++++++------------------------------ 1 file changed, 17 insertions(+), 52 deletions(-) diff --git a/pi/install/pi_profile.sh b/pi/install/pi_profile.sh index 526d8d53..599f114c 100644 --- a/pi/install/pi_profile.sh +++ b/pi/install/pi_profile.sh @@ -113,8 +113,20 @@ install_google_tts_assets() { 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="" - local member + 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" @@ -123,55 +135,8 @@ install_google_tts_assets() { tmp_dir="$(mktemp -d)" log "Downloading Google Chrome TTS assets..." - if ! curl -L -o "${tmp_dir}/googletts-26.5.tar.xz" "$dist_url"; then - rm -rf "$tmp_dir" - log "WARNING: failed to download Google Chrome TTS assets; chromegtts will be unavailable" - return - fi - - # The ChromeOS tarball has had architecture-specific library names. The Pi - # profile historically hard-coded ARM names; the laptop profile should be able - # to use the same installer when a matching x86_64 library exists, and should - # fail soft when it does not. - local -a candidate_libs=() - case "$(uname -m)" in - aarch64|arm64) - candidate_libs=(libchrometts_arm64.so libchrometts.so) - ;; - armv7l|armhf) - candidate_libs=(libchrometts_armv7.so libchrometts.so) - ;; - x86_64|amd64) - candidate_libs=(libchrometts_x86_64.so libchrometts_amd64.so libchrometts_x64.so libchrometts.so) - ;; - i386|i686) - candidate_libs=(libchrometts_x86.so libchrometts_i386.so libchrometts.so) - ;; - *) - log "WARNING: unsupported Chrome TTS architecture $(uname -m); skipping Google TTS assets" - rm -rf "$tmp_dir" - return - ;; - esac - - for member in "${candidate_libs[@]}"; do - if tar -tf "${tmp_dir}/googletts-26.5.tar.xz" "$member" >/dev/null 2>&1; then - lib_member="$member" - break - fi - done - - if [[ -z "$lib_member" ]]; then - log "WARNING: no libchrometts library matching $(uname -m) found in Google TTS archive; chromegtts will be unavailable" - rm -rf "$tmp_dir" - return - fi - - if ! tar -xf "${tmp_dir}/googletts-26.5.tar.xz" -C "$tmp_dir" en-us-x-multi.zvoice "$lib_member"; then - rm -rf "$tmp_dir" - log "WARNING: failed to unpack Google Chrome TTS assets; chromegtts will be unavailable" - return - fi + 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" @@ -182,7 +147,7 @@ install_google_tts_assets() { 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 using $lib_member" + log "Installed Google Chrome TTS assets to $asset_dir" } install_pi_profile() { From 55e8235b02d698aa30f0136110bc3ee5047f4b7c Mon Sep 17 00:00:00 2001 From: legop3 <46182676+legop3@users.noreply.github.com> Date: Tue, 7 Jul 2026 00:23:50 -0400 Subject: [PATCH 9/9] Keep laptop Google TTS changes out of Pi profile --- pi/install/debian_laptop_profile.sh | 79 +++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/pi/install/debian_laptop_profile.sh b/pi/install/debian_laptop_profile.sh index 439fe2a1..1ab64c8f 100644 --- a/pi/install/debian_laptop_profile.sh +++ b/pi/install/debian_laptop_profile.sh @@ -69,14 +69,87 @@ install_debian_laptop_audio_support() { install -m 0644 pi/asound.debian-laptop.conf /etc/asound.conf log "Installed dedicated Debian laptop ALSA config to /etc/asound.conf" - install -D -o root -g root -m 0755 pi/bin/chromegtts-daemon.py /usr/local/bin/chromegtts-daemon - log "Installed chromegtts daemon" + install -D -o root -g root -m 0755 pi/bin/chromegtts-daemon-laptop.py /usr/local/bin/chromegtts-daemon + log "Installed laptop chromegtts daemon" - install_google_tts_assets + install_google_tts_assets_laptop log "ALSA config updated; reboot recommended before testing laptop rover audio" } +install_google_tts_assets_laptop() { + 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="" + local member + + 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 for Debian laptop profile..." + if ! curl -L -o "${tmp_dir}/googletts-26.5.tar.xz" "$dist_url"; then + rm -rf "$tmp_dir" + log "WARNING: failed to download Google Chrome TTS assets; chromegtts will be unavailable" + return + fi + + local -a candidate_libs=() + case "$(uname -m)" in + aarch64|arm64) + candidate_libs=(libchrometts_arm64.so) + ;; + armv7l|armhf) + candidate_libs=(libchrometts_armv7.so) + ;; + x86_64|amd64) + candidate_libs=(libchrometts_x86_64.so libchrometts_amd64.so libchrometts_x64.so libchrometts.so) + ;; + i386|i686) + candidate_libs=(libchrometts_x86.so libchrometts_i386.so libchrometts.so) + ;; + *) + log "WARNING: unsupported Chrome TTS architecture $(uname -m); skipping Google TTS assets" + rm -rf "$tmp_dir" + return + ;; + esac + + for member in "${candidate_libs[@]}"; do + if tar -tf "${tmp_dir}/googletts-26.5.tar.xz" "$member" >/dev/null 2>&1; then + lib_member="$member" + break + fi + done + + if [[ -z "$lib_member" ]]; then + log "WARNING: no libchrometts library matching $(uname -m) found in Google TTS archive; chromegtts will be unavailable" + rm -rf "$tmp_dir" + return + fi + + if ! tar -xf "${tmp_dir}/googletts-26.5.tar.xz" -C "$tmp_dir" en-us-x-multi.zvoice "$lib_member"; then + rm -rf "$tmp_dir" + log "WARNING: failed to unpack Google Chrome TTS assets; chromegtts will be unavailable" + return + fi + + 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 using $lib_member" +} + install_debian_laptop_profile() { install_debian_laptop_deps disable_debian_laptop_desktop_audio_stack