What is the best way to control volume on macOS from Home Assistant

I want to control volume on the iMac from Home Assistant.

For windows computers I know two pretty popular software systems that can be used to solve such task: IOTLink ( https://iotlink.gitlab.io/ ) and WinThing ( https://github.com/msiedlarek/winthing ). Both of them use MQTT as a transport for the commands and info.

I think that I want something similar. A program what is working on macOS that is connected with MQTT server and listening for the commands.

I need to get info about current volume and to change the volume.

What software can you recommend to control volume on macOS system?

I don’t know of any ready-made software, but it seems like you could put something together and leverage osascript to manipulate the volume.

〉osascript -e 'get output volume of (get volume settings)'
19
〉osascript -e 'set volume output volume 50'
〉
〉osascript -e 'get output volume of (get volume settings)'
50
1 Like

I use Airfoil on my Mac, which gives quite a bit of control via AppleScript but also lets you connect to Airplay and bluetooth speakers throughout the home. My entire TTS system is based on Airfoil and broadcasts to all of my HomePods via Airplay.

Airfoil is also nice in that it lets you configure groups of speakers as well and you can really get a pretty nice TTS or whole-house music system doing that. Paired with using Apple Music on the Mac, it’s a pretty powerful solution that I’ve used since it was version 1.0 years ago. Of course with HomePods I can just tell Siri what I want to listen to but do still use Apple Music (which is highly AppleScript-able) for certain automation music scenes.

1 Like

Thank you. I completely agree, that osascript can be used as a base fro what I need, but I will try to find the ready-to-use solution at first.

Well, I haven’t found any project that solved my task, and I’ve written a simple thing that does it:
GitHub - bessarabov/mac2mqtt: View and control some aspects of macOS computer via MQTT

Here is a post with some details about this project: Mac2mqtt — control volume on macOS via MQTT

Do you have any code to share? How did you get AirFoil and HomeAssistant working togeather?
I love AirFoil.

It’s pretty straightforward to do, just a little legwork:

  • Have a fully operational Airfoil system working and connected to your HomePods or other preferred output devices
  • Set the Airfoil source to be Text-To-Speech
  • Configure the system running airfoil to accept inbound SSH commands (I use a Mac, you can follow this tutorial to set it up)
  • Set up Home Assistant for shell scripts that execute the Speak command on the Mac (which is why you set Airfoil for TTS). If using Windows you’ll have to research the best way to do this.

I enhanced mine quite a bit by getting custom voices for my Mac running Airfoil (there’s a company that sells really nice authentic sounding voices, mine is a beautiful Scottish female voice) and also writing a custom script that Home Assistant will run when it SSH’s into that Mac to give me more control over the volume, which voice to use, which speakers to use, and so forth.

I can also use AppleScript commands to run Apple Music for whole-house audio as well (although with HomePods it’s also easy to just say “Play greatest hits everywhere”, the choice is yours.

The bash script I wrote to do everything actually re-connects to the speaker groups I defined in Airfoil so that if we use Siri for anything (playing music, giving the weather, etc) it will disconnect from Airfoil, so all my TTS commands in HASS have a built in 2 second delay to give Airfoil time to reconnect to the speakers I want to broadcast on.

Here is the bash script I wrote (you may want to tweak it for your own purposes):

#!/bin/bash
Speaker=""
Volume_Level=0
Text=""
Voice=""
Source=""
Delay=.75
IsGroup=0
Help=""

while getopts ":t:s:l:v:r:" arg; do
  case $arg in
    t) Text=$OPTARG;;
    s) Speaker=$OPTARG;;
    l) Volume_Level=$OPTARG;;
    v) Voice=$OPTARG;;
    r) Source=$OPTARG;;
  esac
done


# Don't proceed if not text is passed
if [ "$Text" = "" ]
then
  printf "\nCommand line options: \n"
  printf "  -t: Text to speak (required)\n"
  printf "  -s: Speaker or speaker group (defaults to Default group)\n"
  printf "  -l: Optional volume level (only for non grouped speakers)\n"
  printf "  -v: Optional voice (defaults to CereVoice Kirsty)\n"
  printf "  -r: Optional source (i.e., Text to Speech) - NOT IMPLEMENTED\n"
  printf "\n"
  exit
fi

# Set default speaker
if [ "$Speaker" = "" ]
then
  Speaker="Default"
fi

if [ "$Speaker" = "Default" ]; then IsGroup=1; fi
if [ "$Speaker" = "Upstairs" ]; then IsGroup=1; fi
if [ "$Speaker" = "Downstairs" ]; then IsGroup=1; fi

# Set default voice
if [ "$Voice" = "" ]
then
  Voice="CereVoice Kirsty"
fi

# Set default source
if [ "$Source" = "" ]
then
  Source="Text to Speech"
fi

# If using group speakers, give a bit longer delay so they can all connect
if [ $IsGroup -gt 0 ]
then
  Delay=1.5
fi

osascript <<EOD
  tell application "Airfoil"
    disconnect from (every speaker whose name starts with "Default")
    disconnect from (every speaker whose name starts with "Upstairs")
    disconnect from (every speaker whose name starts with "Downstairs")
    disconnect from (every speaker whose name starts with "Basement")
    disconnect from (every speaker whose name starts with "Bedroom")
    disconnect from (every speaker whose name starts with "Office")
    disconnect from (every speaker whose name starts with "Family Room")
    disconnect from (every speaker whose name starts with "Theater")
    disconnect from (every speaker whose name starts with "Gym")
  end tell
  
  delay $Delay
  
  tell application "Airfoil"
    connect to (every speaker whose name starts with "$Speaker")
  end tell
  
  delay $Delay
EOD

if [ $IsGroup -lt 1 ]
then
  if (( $(echo "$Volume_Level > 0" |bc -l) ))
  then
    osascript <<EOD
      tell application "Airfoil"
        set (volume of every speaker whose name starts with "$Speaker") to $Volume_Level
      end tell
EOD
  fi
fi

#Text="$Text. TTS version 1."

say -v "$Voice" "$Text"

if [ "$Speaker" != "Default" ]
then
  osascript <<EOD
    delay 1.5
  
    tell application "Airfoil"
      disconnect from (every speaker whose name starts with "$Speaker")
      delay 1
      connect to (every speaker whose name starts with "Default")
    end tell
EOD
fi

And here is how I execute it using HA shell commands:

speak: ssh -i /config/ssh/id_rsa -o 'StrictHostKeyChecking=no' [username]@[IP address] '~/Scripts/tts_v2.sh -t "{{text}}" -s "{{speaker}}" -l {{volume}} -v "{{voice}}" -s "{{source}}"'

And then my automation:

service: shell_command.speak
data:
  text: The garage door is opening.

This is great, Thanks! Do you happen to have any lovelace cards to control AirFoil with using spotify. Turning on speakers, volume etc?

I don’t but it would be easy to do using bash scripts, AppleScript and SSH to the Airfoil server.

1 Like