move systemd prefixes to software prefixes

This commit is contained in:
2025-11-18 10:01:07 -05:00
parent 91f4687c07
commit 1ae62e70ed
30 changed files with 0 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,100 @@
- name: Install or Update the Wyoming Service
hosts: wyoming
vars_files:
- secrets/vars.yaml
tasks:
- name: Install and upgrade wyoming/wakeword
when: install
block:
# Stop running services before install/upgrade
- name: Stop wakeword service
ansible.builtin.systemd_service:
state: stopped
name: wakeword.service
scope: user
ignore_errors: true
- name: Stop wyoming service
ansible.builtin.systemd_service:
state: stopped
name: wyoming.service
scope: user
ignore_errors: true
# Wyoming Service
- name: Checkout/pull the wyoming repo
ansible.builtin.git:
repo: '{{ wyoming.repo }}'
dest: "{{ ansible_env.HOME }}/wyoming-satellite"
update: yes
- name: Run the script/setup in the wyoming repo
command: "{{ ansible_env.HOME }}/wyoming-satellite/script/setup"
- name: Install audio enhancements in wyoming repo
command: "{{ ansible_env.HOME }}/wyoming-satellite/.venv/bin/pip3 install 'webrtc-noise-gain==1.2.3'"
# Wake word service
- name: Checkout/pull the wyoming repo
ansible.builtin.git:
repo: '{{ wakeword.repo }}'
dest: "{{ ansible_env.HOME }}/wyoming-openwakeword"
update: yes
- name: Run the script/setup in the wakeword repo
command: "{{ ansible_env.HOME }}/wyoming-openwakeword/script/setup"
# Custom sound bites for start/stop listening
- name: Copy sound bites
copy:
src: "{{ item }}"
dest: "{{ ansible_env.HOME }}/wyoming-satellite/sounds/"
with_items:
- listening.wav
- finished.wav
# Custom wake word models
- name: Copy custom models
copy:
src: "{{ item }}"
dest: "{{ ansible_env.HOME }}/wyoming-openwakeword/wyoming_openwakeword/models/{{ item }}"
with_items:
- jarvis_v2.tflite
# Copy and start systemd services
- name: Create ~/.config/systemd/user dir
ansible.builtin.file:
path: "{{ ansible_env.HOME }}/.config/systemd/user"
state: directory
mode: '0755'
- name: Copy wyoming.service
template:
src: wyoming.service
dest: "{{ ansible_env.HOME }}/.config/systemd/user/wyoming.service"
mode: '0644'
- name: Copy wakeword.service
template:
src: wakeword.service
dest: "{{ ansible_env.HOME }}/.config/systemd/user/wakeword.service"
mode: '0644'
- name: Reload wakeword service
ansible.builtin.systemd_service:
state: restarted
name: wakeword.service
enabled: true
daemon_reload: true
scope: user
- name: Reload wyoming service
ansible.builtin.systemd_service:
state: restarted
name: wyoming.service
enabled: true
daemon_reload: true
scope: user
- name: Add debug alias for wakeford
lineinfile:
path: "{{ ansible_env.HOME }}/.bashrc"
line: alias debug-wakeword="journalctl --user -u wakeword -f | grep -E '0\.0[^0].*|0\.[^0].*'"
regexp: '^alias debug-wakeword=.*$'
state: present
insertafter: EOF
create: true
- name: Add debug alias for wyoming
lineinfile:
path: "{{ ansible_env.HOME }}/.bashrc"
line: alias debug-wyoming="journalctl --user -u wyoming -f | grep 'Stopped recording to'"
regexp: '^alias debug-wyoming=.*$'
state: present
insertafter: EOF
create: true

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,18 @@
install: false
wyoming:
repo: https://github.com/rhasspy/wyoming-satellite.git
listen_ip: 0.0.0.0
listen_port: 10700
mic_auto_gain: 31
mic_noise_suppression: 2
wake_uri: tcp://127.0.0.1:10400
wake_word_name: jarvis_v2
wake_refractory_seconds: 1
wakeword:
repo: https://github.com/rhasspy/wyoming-openwakeword.git
listen_ip: 127.0.0.1
listen_port: 10400
threshold: 0.8
preload_model: jarvis_v2

View File

@@ -0,0 +1,18 @@
[Unit]
Description=Wyoming Satellite Wake Word
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
ExecStart={{ ansible_env.HOME }}/wyoming-openwakeword/script/run \
--uri 'tcp://{{ wakeword.listen_ip }}:{{ wakeword.listen_port }}' \
--threshold '{{ wakeword.threshold }}' \
--preload-model '{{ wakeword.preload_model }}' \
--debug-probability
WorkingDirectory={{ ansible_env.HOME }}/wyoming-openwakeword
Restart=always
RestartSec=1
[Install]
WantedBy=default.target

View File

@@ -0,0 +1,279 @@
# Wyoming Satellite
- [Wyoming Satellite](#wyoming-satellite)
- [Data Flow](#data-flow)
- [Install wakeword](#install-wakeword)
- [Install Wyoming](#install-wyoming)
- [Hardware](#hardware)
- [Bluetooth keepalive](#bluetooth-keepalive)
- [Bluetooth autoconnect](#bluetooth-autoconnect)
- [Systemd](#systemd)
- [Debugging](#debugging)
- [Volume](#volume)
- [Community Wake Words](#community-wake-words)
- [Prompts](#prompts)
- [Default](#default)
- [Starship House](#starship-house)
## Data Flow
```mermaid
sequenceDiagram
actor us as User
participant mic as Listening Device
participant ww as Wake Word Model
participant ha as Home Assistant
participant stt as Speech to Text
participant tts as Text to Speech
participant ai as LLM Tool Caller
us ->> mic: Say wakeword
mic ->> ww: Are you > x% confident the wake word was detected?
break When mic didn't detect wake word
ww ->> mic: No
mic ->> mic: keep listening
end
ww ->> mic: Yes
mic ->> us: "listening sound"
us ->> mic: Say request
mic ->> ha: Send audio to Home Assistant
ha ->> stt: Convert this speech to text
stt ->> ha: Here's the text
ha ->> mic: The user is done talking, here's what was said
mic ->> us: "done listening sound"
ha ->> ha: Did the text match any known intent?
alt Yes
ha ->> ha: Do the thing the intent is supposed to do
ha ->> tts: Turn the reply text into speech
tts ->> ha: here's the audio clip
ha ->> mic: play this audio clip
mic ->> user: audio clip
else No
ha ->> ai: Send entities exposed by Home Assistant, TTS, and "Can you figure this out?"
end
```
## Install wakeword
<https://github.com/rhasspy/wyoming-openwakeword/>
Note that --debug-probability will output thresholds for tuning.
```bash
git clone https://github.com/rhasspy/wyoming-openwakeword.git
uv venv --python 3.9
source .venv/bin/activate
uv pip install .
# Copy the model
cp ~/Homelab/active/systemd_wyoming/jarvis_v2.tflite ~/wyoming-openwakeword/wyoming_openwakeword/models
# typical wake word command
script/run \
--uri 'tcp://127.0.0.1:10400' \
--threshold '0.8' \
--preload-model 'jarvis_v2' \
# --debug-probability
```
## Install Wyoming
<https://github.com/rhasspy/wyoming-satellite>
```bash
git clone https://github.com/rhasspy/wyoming-satellite.git
uv venv --python 3.9
source .venv/bin/activate
uv pip install .
uv pip install webrtc-noise-gain==1.2.3
# Copy listen and done sounds
cp ~/Homelab/active/systemd_wyoming/{listening.wav,finished.wav} ~/wyoming-satellite/sounds
# typical wyoming command
# Add wake-uri and wake-word-name to your wyoming run
script/run \
--name 'Living Room' \
--uri 'tcp://0.0.0.0:10700' \
--mic-command 'arecord -r 16000 -c 1 -f S16_LE -t raw' \
--snd-command 'aplay -r 22050 -c 1 -f S16_LE -t raw' \
--awake-wav sounds/listening.wav \
--done-wav sounds/finished.wav \
--synthesize-command tee \
--transcript-command tee \
--wake-uri 'tcp://127.0.0.1:10400' \
--wake-word-name 'hey jarvis' \
--wake-refractory-seconds 1
# Allow through firewall
sudo firewall-cmd --new-zone=wyoming --permanent
sudo firewall-cmd --zone=wyoming --add-source=10.2.0.230/32 --permanent
sudo firewall-cmd --zone=wyoming --add-port=10700/tcp --permanent
sudo firewall-cmd --reload
```
## Hardware
Finding available speakers/microphones
```bash
# Find microphone
arecord -L | grep plughw -A 2
# Create a test recording
arecord -D plughw:CARD=Speaker,DEV=0 -r 16000 -c 1 -f S16_LE -t wav -d 5 test.wav
# Find speaker
aplay -L | grep plughw -A 2
# Play test recording
aplay -D plughw:CARD=Speaker,DEV=0 test.wav
# Change audio levels
alsamixer
# Save audio levels
sudo alsactl store
```
Bluetooth
```bash
bluetoothctl
discoverable on
pairable on
agent on
default-agent
scan on
```
Then wait for `[NEW] Device 12:23:34:45:56:67 devicename`
```bash
pair 12:23:34:45:56:67
```
The bluetooth speaker should now work with `aplay`.
### Bluetooth keepalive
Bluetooth speakers have a tendency to sleep and then take a bit to wake up. We can force
them to stay alive permanently by constantly playing an inaudible sound onloop.
```bash
# generate our 48000kHz sample rate synth 1 second sin 10hz wave file at -20dB so it's quiet
# This takes some tweaking depending on the speaker. A 1hz wave didn't work on my bluetooth speaker,
# but 10hz did.
sudo sox -V -r 48000 -n -b 16 -c 2 /usr/share/sounds/silence.wav synth 1 sin 10 vol -20dB
# Copy that to /usr/share/sounds
sudo cp silence.wav /usr/share/sounds
```
Create a script at `/usr/local/bin/speakeralive.sh`
```bash
#!/bin/bash
while true; do aplay /usr/share/sounds/silence.wav; done
```
Then create a systemd service at `~/.config/systemd/user/speakeralive.service`
```conf
[Unit]
Description=Plays an inaudible noise continuously to keep a speaker alive
After=network.target
Wants=network-online.target
[Service]
Restart=always
Type=simple
ExecStart=/usr/local/bin/speakeralive.sh
[Install]
WantedBy=bluetooth.target
```
And enable the service
```bash
systemctl --user enable --now speakeralive
```
### Bluetooth autoconnect
In order to automatically connect a bluetooth speaker at boot you'll need a oneshot service
Create `~/.config/systemd/user/speakerconnect.service`
```conf
[Unit]
Description=Connects a bluetooth speaker on boot
After=bluetooth.target
[Service]
Restart=on-failure
Type=oneshot
RestartSec=5
ExecStart=bluetoothctl connect A1:B2:C3:D4...
[Install]
WantedBy=bluetooth.target
```
Enable it
```bash
systemctl --user enable --now speakerconnect.service
```
## Systemd
```bash
# Make sure to enable login linger before installing
sudo loginctl enable-linger ducoterra
```
```bash
ansible-playbook \
-i ansible/inventory.yaml \
active/systemd_wyoming/install_wyoming.yaml \
-e "@active/systemd_wyoming/vars.yaml"
```
## Debugging
1. Make sure you have `--debug` passed to wyoming
2. Make sure you have `--debug-probability` passed to wakeword
3. Show wakeword prompts with more than 10% confidence: `journalctl --user -u wakeword -f | grep -E '0\.0[^0].*|0\.[^0].*'`
4. Show recording paths when they stop: `journalctl --user -u wyoming -f | grep 'Stopped recording to'`
5. Play recordings: `aplay /path/to/recording`
## Volume
Run `alsamixer`.
Use `showkey` to see which key is being triggered by your volume device.
## Community Wake Words
<https://github.com/fwartner/home-assistant-wakewords-collection?tab=readme-ov-file>
<https://github.com/fwartner/ha-openwakeword-installer>
1. Download the custom model to this directory
2. Add it to install_wyoming.yaml
## Prompts
### Default
You are a voice assistant for Home Assistant.
Answer questions about the world truthfully.
Answer in plain text. Keep it simple and to the point.
### Starship House
You are a voice assistant for Home Assistant.
Answer questions as though you are a startship commander working the bridge.
Answer in plain text. Keep it simple and to the point.
There is a life threatening emergency on the ship. This is more important than our request.

View File

@@ -0,0 +1,30 @@
[Unit]
Description=Wyoming Satellite
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
ExecStart={{ ansible_env.HOME }}/wyoming-satellite/script/run \
--name '{{ ansible_hostname }}' \
--uri 'tcp://{{ wyoming.listen_ip }}:{{ wyoming.listen_port }}' \
--mic-command 'arecord -r 16000 -c 1 -f S16_LE -t raw {% if hostvars[inventory_hostname].mic_device %}-D {{hostvars[inventory_hostname].mic_device}}{% endif %}' \
--snd-command 'aplay -r 22050 -c 1 -f S16_LE -t raw {% if hostvars[inventory_hostname].speaker_device %}-D {{hostvars[inventory_hostname].speaker_device}}{% endif %}' \
--awake-wav sounds/listening.wav \
--done-wav sounds/finished.wav \
--timer-finished-wav sounds/timer_finished.wav \
--mic-auto-gain {{ wyoming.mic_auto_gain }} \
--mic-noise-suppression {{ wyoming.mic_noise_suppression }} \
--mic-volume-multiplier {{ hostvars[inventory_hostname].mic_volume_multiplier }} \
--wake-uri '{{ wyoming.wake_uri }}' \
--wake-word-name '{{ wyoming.wake_word_name }}' \
--wake-refractory-seconds {{ wyoming.wake_refractory_seconds }} \
--timer-finished-wav-repeat 10 1 \
--debug \
--debug-recording-dir /tmp
WorkingDirectory={{ ansible_env.HOME }}/wyoming-satellite
Restart=always
RestartSec=1
[Install]
WantedBy=default.target