rhasspy / wyoming-satellite

Remote voice satellite using Wyoming protocol
MIT License
587 stars 85 forks source link

Feature Request: Adjust satellite speaker volume in Home Assistant #106

Open khalob opened 7 months ago

khalob commented 7 months ago

Great work, love what you do as always. I know this is an "in progress" situation, but installing this got me thinking:

It would be nice to be able to adjust the --snd-volume via a slider in Home assistant or some external means. (See existing home assistant slider example image bellow) image

Use case: sometimes I want my speaker loud, sometimes I do not. Ideally I would adjust it via voice commands :)

llluis commented 7 months ago

That's a great idea!

image

It's surely possible. Just a matter to find the best solution.

cooljimy84 commented 6 months ago

Wouldn't an auto volume level based on the mic levels be as good ? Then if the noise in the room was loud it would be high volume. Like the mycroft auto volume

gericho commented 5 months ago

A temporary workaround that works system-wide is to SSH into the satellite:

alsamixer

  1. press F6 and select seeed-2mic-voice-card
  2. using arrow keys, select the first "speaker" entry and lower the volume with up and down
  3. press ESC to exit

I hope will be possible to expose the volume slider to home assistant BTW as mentioned above.

cooljimy84 commented 5 months ago

So another idea is to ssh in load a alsa profile? alsactl -f /home/pi/asound.state restore You could have different ones for each volume level...

TN-1 commented 3 months ago

I have worked a volume control method using Node-RED and MQTT. It is basic, and could use some polish, and it doesnt integrate brilliantly, but atleast there is volume control accessible inside home assistant.

This is installed on a Pi Zero 2 W setup as per https://github.com/rhasspy/wyoming-satellite/blob/master/docs/tutorial_2mic.md

Screenshot_20240618_190438

Screenshot_20240618_190523

Node-RED Flow: ``` [ { "id": "41e3456dc37ed0f3", "type": "tab", "label": "MQTT Volume Control", "disabled": false, "info": "", "env": [] }, { "id": "716e43981a4c6692", "type": "mqtt out", "z": "41e3456dc37ed0f3", "name": "", "topic": "homeassistant/wyoming-sats/1/volume", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "139d13b5bdc9f9cc", "x": 990, "y": 60, "wires": [] }, { "id": "77a648ab4e4cd7f7", "type": "exec", "z": "41e3456dc37ed0f3", "command": "awk -F\"[][]\" '/Left:/ { print $2 }' <(amixer -c 1 sget Speaker)", "addpay": "", "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "name": "Get speaker info", "x": 340, "y": 60, "wires": [ [ "ae6179f1de9d5b21" ], [], [] ] }, { "id": "ae6179f1de9d5b21", "type": "change", "z": "41e3456dc37ed0f3", "name": "Remove %", "rules": [ { "t": "change", "p": "payload", "pt": "msg", "from": "%", "fromt": "str", "to": "", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 530, "y": 60, "wires": [ [ "23e9aae98e36d6a9" ] ] }, { "id": "036efcd6cab6b383", "type": "inject", "z": "41e3456dc37ed0f3", "name": "Once a second...", "props": [], "repeat": "1", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 150, "y": 60, "wires": [ [ "77a648ab4e4cd7f7" ] ] }, { "id": "23e9aae98e36d6a9", "type": "rbe", "z": "41e3456dc37ed0f3", "name": "Has value changed?", "func": "rbe", "gap": "", "start": "", "inout": "out", "septopics": true, "property": "payload", "topi": "topic", "x": 720, "y": 60, "wires": [ [ "716e43981a4c6692" ] ] }, { "id": "6ed48981d0f2a775", "type": "mqtt in", "z": "41e3456dc37ed0f3", "name": "", "topic": "homeassistant/wyoming-sats/1/set_volume", "qos": "2", "datatype": "auto-detect", "broker": "139d13b5bdc9f9cc", "nl": false, "rap": true, "rh": 0, "inputs": 0, "x": 240, "y": 120, "wires": [ [ "f1a9bf8da243ea10" ] ] }, { "id": "5531664312c6ed5f", "type": "exec", "z": "41e3456dc37ed0f3", "command": "amixer -c 1 sset Speaker ", "addpay": "payload", "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "name": "Set volume", "x": 650, "y": 120, "wires": [ [], [], [] ] }, { "id": "f1a9bf8da243ea10", "type": "change", "z": "41e3456dc37ed0f3", "name": "Add %", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "payload & \"%\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 490, "y": 120, "wires": [ [ "5531664312c6ed5f" ] ] }, { "id": "139d13b5bdc9f9cc", "type": "mqtt-broker", "name": "", "broker": "", "port": "1883", "clientid": "", "autoConnect": true, "usetls": false, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "autoUnsubscribe": true, "birthTopic": "", "birthQos": "0", "birthRetain": "false", "birthPayload": "", "birthMsg": {}, "closeTopic": "", "closeQos": "0", "closeRetain": "false", "closePayload": "", "closeMsg": {}, "willTopic": "", "willQos": "0", "willRetain": "false", "willPayload": "", "willMsg": {}, "userProps": "", "sessionExpiry": "" } ] ```
Home assistant configuration: ``` mqtt: number: - unique_id: e0306378-840f-494b-ab71-df9248f228e4 name: "Satellite 1 Volume" command_topic: "homeassistant/wyoming-sats/1/set_volume" state_topic: "homeassistant/wyoming-sats/1/volume" value_template: "{{value}}" min: 70 max: 100 step: 1 ```
eleaner commented 2 months ago

the temporary solution above looks awesome, but could anyone please explain it in more simple terms?

TN-1 commented 2 months ago

I will make a gist about the process including some updates I have made. I will post here when available. I am doing some background research into including it natively in the project (not in its current form), but I am a bit time short at the moment so it wont be soon.

For now, Node-Red needs to be installed on the Pi (https://nodered.org/docs/getting-started/raspberrypi). In the Node-Red interface, you can import the Flow I included above (Some changes need to be made for the MQTT broker). Then in Home Assistant, you add the configuration block to your configuation.yaml.

formatBCE commented 2 months ago

You can do

amixer -M -c seeed2micvoicec set Speaker 10%

instead of alsamixer. That could be set to shell command in HA, and bound to input_number with automation. Not that i want to do it, i guess it would be much better to have inbuilt solution - but this is possible.

LazzaAU commented 4 weeks ago

just going to add this here as it may help someone else also shrug. These are the steps i just took to do exactly that.

How to Control Raspberry Pi Volume from Home Assistant

This guide will help you set up your Raspberry Pi volume control in Home Assistant. for example when using wyoming assist on a raspberry pi

Step 1: Set Up SSH Access

  1. Generate SSH Key: If you haven’t already, generate an SSH key pair on your HA server. in the Home assistant terminal and SSH addon type:
    ssh-keygen -t rsa -b 4096 -f /config/.ssh/id_rsa
  2. Copy the Public Key to the Pi: Copy the public key to your Raspberry Pi to enable passwordless SSH. in the terminal and SSH addon type: ( replace with your assist satellite's IP. eg: pi@192.168.0.42)
    ssh-copy-id -i /config/.ssh/id_rsa.pub pi@<Raspberry_Pi_IP>
  3. Test SSH Connection: Ensure that you can SSH into the Pi from HA without a password. In the terminal and SSH addon type:
    ssh -i /config/.ssh/id_rsa pi@<Raspberry_Pi_IP>

Step 2: Check your Pi's Sound Controls

  1. Determine your Pi's PCM volume control: You'll need to find if your amixer uses c1, c2, or something else. To do that, go to your Raspberry Pi terminal and type:
    amixer controls

    Look for something that says 'PCM Playback Volume.' The start of that line will indicate the numid, which could be 0, 1, or another number. For example, if it says 0, then in the Pi terminal type:

    amixer -c 0 sset PCM 90

    Hit enter, and you should see a result like this:

    Simple mixer control 'PCM',0
    Capabilities: pvolume pvolume-joined pswitch pswitch-joined
    Playback channels: Mono
    Limits: Playback -10239 - 400
    Mono: Playback 90 [97%] [0.90dB] [on]

    If that works and the playback shows 90 or whatever number you chose, then continue with the steps below.

Step 3: Set Up Volume Monitoring with MQTT

  1. Create the Monitor Script: On your Raspberry Pi, create a script to monitor the ALSA volume and publish changes to an MQTT topic. Save the following script as /usr/local/bin/monitor_volume.sh: remember to change the pi address of your HA MQTT broker, username and password and the amixer c1 number to the number you determined earlier.

    #!/bin/bash
    
    BROKER_ADDRESS="<your-HA-MQTT-Broker-IP>"
    MQTT_TOPIC="home/assist/volume"
    MQTT_USERNAME="<your-MQTT-username>"
    MQTT_PASSWORD="Your-MQTT-Password>"
    PREVIOUS_VOLUME=""
    
    while true; do
       CURRENT_VOLUME=$(amixer -c 1 get PCM | grep -oP "\[\d+%\]" | head -n1 | tr -d "[]%")
    
       if [ "$CURRENT_VOLUME" != "$PREVIOUS_VOLUME" ]; then
           mosquitto_pub -h "$BROKER_ADDRESS" -t "$MQTT_TOPIC" -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -m "$CURRENT_VOLUME"
           PREVIOUS_VOLUME="$CURRENT_VOLUME"
       fi
    
       sleep 5
    done

    1.2 In Home assistant, edit the configuration.yaml and add this under mqtt: sensor

mqtt:
  sensor:
    - name: Speaker Volume
      state_topic: "home/assist/volume"
  1. Set Up the Systemd Service: Create a systemd service to run the monitoring script automatically. Save the following as /etc/systemd/system/monitor_volume.service on your assist pi:

    [Unit]
    Description=Monitor ALSA Volume and Send MQTT Messages
    
    [Service]
    ExecStart=/usr/local/bin/monitor_volume.sh
    Restart=always
    User=pi
    
    [Install]
    WantedBy=multi-user.target
  2. Enable and Start the Service: Enable and start the service so that it runs on boot and monitors the volume continuously.

    sudo systemctl enable monitor_volume.service
    sudo systemctl start monitor_volume.service

Step 4: Configure HA to Control the Pi's Volume

  1. Add Shell Commands: In your configuration.yaml, add shell commands to control the volume. NOTE: Replace pi@192.168.0.42 with your Pi's IP address as per Step 1.2) and replace amixer c 1 with whatever number you determined in Step 2.

    shell_command:
     volume_up: ssh -i /config/.ssh/id_rsa -o StrictHostKeyChecking=no pi@192.168.0.42 'amixer -c 1 sset PCM 5%+'
     volume_down: ssh -i /config/.ssh/id_rsa -o StrictHostKeyChecking=no pi@192.168.0.42 'amixer -c 1 sset PCM 5%-'
     volume_mute: ssh -i /config/.ssh/id_rsa -o StrictHostKeyChecking=no pi@192.168.0.42 'amixer -c 1 sset PCM toggle'
     set_volume: >
       ssh -i /config/.ssh/id_rsa -o StrictHostKeyChecking=no pi@192.168.0.42 'amixer -c 1 sset PCM {{ volume }}%'
  2. Add an Input Number for Volume Control: In the same configuration.yaml, add an input number to create a slider for volume control.

    input_number:
     speaker_volume:
       name: Speaker Volume
       initial: 50
       min: 0
       max: 100
       step: 1

Step 5: Create Automations

  1. Set Volume from the Slider: Create an automation to set the Pi's volume whenever the slider is adjusted.
    automation:
    - id: 'set_pi_volume'
     alias: Set Pi Volume
     trigger:
       - platform: state
         entity_id: input_number.speaker_volume
     action:
       - service: shell_command.set_volume
         data_template:
           volume: "{{ states('input_number.speaker_volume') | int }}"

Step 6: Control Volume with HA

  1. Use the Slider: The input_number.speaker_volume entity will now reflect the Pi’s volume, and you can adjust the volume using this slider in the HA UI.
  2. Create Scripts or Automations for Volume Control: You can create scripts or additional automations to call the volume_up, volume_down, volume_mute, and set_volume shell commands as needed.

Here's an example automation that will sync the slider to whatever the assist speaker currently is set to:

- id: '1720785157943'
  alias: Sync Speaker Volume
  description: Sync the input_number with the sensor volume
  trigger:
  - platform: state
    entity_id: sensor.speaker_volume
  condition: []
  action:
  - data_template:
      entity_id: input_number.speaker_volume
      value: "{% if states('sensor.speaker_volume') not in ['unknown', 'unavailable'] %}
  {{ states('sensor.speaker_volume') | int(default=50) }}
{% else %}
  00
{% endif %}"
"
    action: input_number.set_value
  mode: single

In theory, you could also ask Assist to set speaker volume to 80%, although I haven't tried this yet, but there's probably no reason why you couldn't—get creative :)

Step 7: Test the Setup

  1. Test the Volume Control: Adjust the volume using the slider in HA and ensure that it controls the volume of your Raspberry Pi correctly.

EDITED: was missing a -c in original post, updated to to add the - , good catch thanks @ejpenney

codemonkey2k5 commented 2 weeks ago

When I check amixer controls I get the following:

tony@Aida-livingroom:~/wyoming-satellite $ amixer controls numid=4,iface=MIXER,name='Master Playback Switch' numid=3,iface=MIXER,name='Master Playback Volume' numid=2,iface=MIXER,name='Capture Switch' numid=1,iface=MIXER,name='Capture Volume'

When I type in amixer, it says my volume is at 90% but you can only just barely hear anything from my speaker.

tony@Aida-livingroom:~/wyoming-satellite $ amixer Simple mixer control 'Master',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 65536 Mono: Front Left: Playback 59000 [90%] [on] Front Right: Playback 59000 [90%] [on] Simple mixer control 'Capture',0 Capabilities: cvolume cswitch cswitch-joined Capture channels: Front Left - Front Right Limits: Capture 0 - 65536 Front Left: Capture 65536 [100%] [on] Front Right: Capture 65536 [100%] [on]

My setup is Wyoming Satellite, running on a raspberry Pi 4. I am using a USB mic and open wakeword. This part works fine, but for the life of me I can not get any audio out. HA logs show that TTS was sent to my pi.

My Satellite config is:

[Unit] Description=Wyoming Satellite Wants=network-online.target After=network-online.target

[Service] Type=simple ExecStart=/home/tony/wyoming-satellite/script/run --name 'Ada-Livingroom' --uri 'tcp://0.0.0.0:10700' --mic-command 'arecord -D plughw:CARD=JV601,DEV=0 -r 16000 -c 1 -f S16_LE -t raw' --snd-command 'aplay -r 22050 -c 1 -f S16_LE -t raw' WorkingDirectory=/home/tony/wyoming-satellite Restart=always RestartSec=1

[Install] WantedBy=default.target

formatBCE commented 2 weeks ago

@codemonkey2k5 did you try this https://github.com/rhasspy/wyoming-satellite/issues/106#issuecomment-2233497024 ?

It works for me. Just use 100% there.

codemonkey2k5 commented 2 weeks ago

I did try that, but it gave me an error about not existing. Honestly, it would be better if I could just run the satellite on my windows pc as a service. I'm in front of it all day and it has a mic and speaker that I know work well. I have python installed (running Ollama server) so I bet it can be done, but I only know enough to be dangerous when it comes to python. And I couldn't find any examples of anyone else doing it.

ejpenney commented 2 weeks ago

Thanks @LazzaAU ! This was super helpful, a couple of notes I ran into after doing C+P from your guide:

Example command:

amixer c 0 sset PCM 90

Should read:

amixer -c 0 sset PCM 90%

Might be worth noting some users may need to modify the field "User=" in /etc/systemd/system/monitor_volume.service, if using non-default users.

Also, I modified the script to have the volume value retained in MQTT (-r) and use the last published value as PREVIOUS_VOLUME. This is useful because if volume isn't changed for several hours MQTT may mark it as unknown. Getting the previous value from MQTT adds additional assurance that the value is accurate and the script/MQTT haven't (somehow) gotten out of sync. This happened a couple of times in the hour I was playing with it, so I'm paranoid.

Granted, this service is a little less polite, querying every 5 seconds instead of only when it believes something has changed.

#!/bin/bash

BROKER_ADDRESS="<your-HA-MQTT-Broker-IP>"
MQTT_TOPIC="home/assist/volume"
MQTT_USERNAME="<your-MQTT-username>"
MQTT_PASSWORD="Your-MQTT-Password>"
PREVIOUS_VOLUME=""

while true; do
    CURRENT_VOLUME=$(amixer -c 1 get PCM | grep -oP "\[\d+%\]" | head -n1 | tr -d "[]%")

    if [ "$CURRENT_VOLUME" != "$PREVIOUS_VOLUME" ]; then
        mosquitto_pub -h "$BROKER_ADDRESS" -t "$MQTT_TOPIC" -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" -m "$CURRENT_VOLUME" -r
    fi

    sleep 5
    PREVIOUS_VOLUME=$(mosquitto_sub -h "$BROKER_ADDRESS" -t "$MQTT_TOPIC" -u "$MQTT_USERNAME" -P $MQTT_PASSWORD -C 1)
done