Automatically turn off stereo when music is not playing

Hello everyone,
first time I post something I did, I hope it helps somebody else and that all the instructions are clear.

Context:

  • I use an amplifier in my livingroom to play music from my smartphone;
  • As “connection” I use a raspberry running one of the many solutions available to have an Apple AirPlay server equivalent (see this for the software and this for the hardware);
  • My stereo is connected to a wifi smart-plug that is controlled from my ha server, so that i can turn on and off the stereo from ha.

Problem:

  • I always forget to turn off the stereo ending up wasting a lot of energy;
  • I’m lazy and I don’t want to turn on the stereo through home-assistant every time I want to listen to music.

Solution:

  • I set-up a ~5 lines script on the raspberry running the AirPlay server - it published on a MQTT topic a message stating whether the raspberry is playing any music or not;
  • On ha I set up a simple automation to:
    • turn on the stereo if it is off and there is music playing;
    • turn off the stereo if no music is playing for >10 minutes.

How-to
Begin the set-up on the raspberry running the AirPlay server:

apt install mosquitto-clients
nano /root/checkaudio.sh

Content of checkaudio.sh:

#!/bin/bash
if [[ $(cat /proc/asound/card0/pcm0p/sub0/status | grep state) = "state: RUNNING" ]]
then
    mosquitto_pub -h your_mqtt_broker_ip --topic "/home-assistant/stereoplaying" -m "1" --username your_mqtt_broker_username --pw your_mqtt_broker_password
else
    mosquitto_pub -h your_mqtt_broker_ip --topic "/home-assistant/stereoplaying" -m "0" --username your_mqtt_broker_username --pw your_mqtt_broker_password
fi

Continue with the set-up:

chmod 655 checkaudio.sh
nano /etc/systemd/system/[email protected] 

Content of [email protected]:

[Unit]
Description=Check audio

[Service]
Type=oneshot
ExecStart=/root/checkaudio.sh

Continue with set-up:

nano /etc/systemd/system/[email protected]

Content of [email protected]:

[Timer]
OnUnitActiveSec=1s
AccuracySec=1ms
[email protected]
OnBootSec:1

Continue with set-up:

systemctl enable [email protected]

Now reboot your system:

shutdown -r now

You chan check whether the mqtt message is being publihed on the topic specified (i.e., whether the script is really telling to home-assistant “hey I’m playing something” or not) issuing this command on the machine where the sound is playing:

mosquitto_sub -h your_mqtt_broker_ip --topic "/home-assistant/stereoplaying" --username your_mqtt_broker_username --pw your_mqtt_broker_password

If there is sound being played, you should continuously see “1” appear on the output, otherwise if no sound is playing you should see “0”.

Now it’s time to add the component to home-assistant configuration. Contents to add to configuration.yaml:

binary_sensor:
   platform: mqtt
   name: "Stereo playing"
   state_topic: "/home-assistant/stereoplaying"
   payload_on: "1"
   payload_off: "0"

Set-up the automations you prefer. I have two automations: i) turn off the stereo if nothing is playing for 10 minutes, ii) turn on the stereo as soon as something is playing. I created the two automations through the new automations interface:

- id: 'xxxxxxx'
  alias: Turn off stereo if music not playing
  trigger:
  - entity_id: binary_sensor.stereo_playing
    for:
      minutes: 10
    from: 'on'
    platform: state
    to: 'off'
  action:
  - data:
      entity_id: group.stereo
    service: homeassistant.turn_off
- id: 'xx'
  alias: Turn on stereo if music playing
  trigger:
  - entity_id: binary_sensor.stereo_salotto_playing
    from: 'off'
    platform: state
    to: 'on'
  action:
  - alias: ''
    data:
      entity_id: group.stereo
    service: homeassistant.turn_on

Now you just need to restart your home-assistant instance.

image

3 Likes

Sorry to necro this thread, but I found your post very useful, and the title is just perfect. I used it as my starting point, however my script has now evolved, so I thought I’d post it here.

I was concerned about the script hammering the system with commands every second, this sometimes makes it difficult to look for processes in system monitor.

Instead, I migrated to an event-driven approach. It listens to PulseAudio events, and acts when sink inputs are added or removed. (Since I use Ubuntu Studio, the script looks for sink-inputs for Jack outputs, but this could be modified - see comments in the code).

Also, it only sends an mqtt message if the status of audio playing has changed.

The upsides here are:

  • a bunch of commands is only run when pertinent audio infrastructure events are detected.
  • mqtt (and thus HA) is not peppered with messages every second

Also, I didn’t need to set up a service, just have the desktop environment launch the script on login.

Another issue for me is that i have a bluetooth stereo in my livingroom, and control it from my PC. When I connect to it, I don’t want the default audio to go to it, just the audio I then specify (I can select audio output in my music player). So the code below includes a command to prevent PulseAudio from switching default audio when connecting a new audio output. This behavior can be removed if not wanted.

My checkaudio.sh:

#!/bin/bash

# Current status is 2 so that an mqtt message will always be sent on startup of this script
CURRENT_STATUS=2
NEW_STATUS=0

MQTT_HOST="your_mqtt_broker_ip"
MQTT_TOPIC="/home-assistant/stereoplaying"
MQTT_USER="your_mqtt_broker_username"
MQTT_PASS="your_mqtt_broker_password"

function sendStateToMqtt() {
  local STATE=$1
  /usr/bin/mosquitto_pub -h "$MQTT_HOST" --topic "$MQTT_TOPIC" -m "$STATE" --username "$MQTT_USER" --pw "$MQTT_PASS"
}

function handleSinkInputs() {
  # Find how many sink-inputs connected to jack_out are available
  local SINK_INPUTS=$(pacmd list-sink-inputs | grep 'sink:[^<]*<jack_out>' | wc -l)
  # Comment above row and uncomment next row if not using Jack
  # local SINK_INPUTS=$(/usr/bin/pacmd list-sink-inputs | head -n 1 | cut -d ' ' -f 1)

  if [[ ${SINK_INPUTS} > 0 ]] ; then
    NEW_STATUS=1
  else
    NEW_STATUS=0
  fi

  if [ "${CURRENT_STATUS}" != "${NEW_STATUS}" ] ; then
    CURRENT_STATUS=${NEW_STATUS}
    sendStateToMqtt ${CURRENT_STATUS}
  fi
}

# Prevent switching the default sink to bluetooth if connected
function setDefaultSink() {
  # If not using jack, modify next line, or replace with just a return command
  /usr/bin/pactl set-default-sink jack_out
}

function handleSinkEvents() {
  local EVENT=$1
  local NUMBER=$2
  
  case ${EVENT} in
    new | remove)
      echo "- Handling event: ${EVENT} sink ${NUMBER}"
      setDefaultSink
      ;;
    
    *)
      ;;
  esac
}

function handleSinkInputEvents() {
  local EVENT=$1
  local NUMBER=$2
  
  case ${EVENT} in
    new | remove)
      echo "- Handling event: ${EVENT} sink-input ${NUMBER}"
      handleSinkInputs
      ;;
    
    *)
      ;;
  esac
}

function handleEvents() {
  while read -r LINE; do
    # Sample event looks like this: "Event 'new' on sink-input #893"
    # Replace spaces and quotes with underscore to make regex matching easier
    local REPLACED=${LINE//[ \']/_}
    if [[ "$REPLACED" =~ ^Event__([^_]+)__on_([^_]+)_#([0-9]+) ]]; then
      local EVENT=${BASH_REMATCH[1]}
      local TARGET=${BASH_REMATCH[2]}
      local NUMBER=${BASH_REMATCH[3]}
      echo "Parsed event: ${EVENT} ${TARGET} ${NUMBER}"
      
      case ${TARGET} in
        sink)
          handleSinkEvents ${EVENT} ${NUMBER}
          ;;
        
        sink-input)
          handleSinkInputEvents ${EVENT} ${NUMBER}
          ;;
        
        *)
          ;;
        
      esac
    else
      echo "Could not parse event"
    fi 
  done
}

##############################################################
# Main program
##############################################################

handleSinkInputs

while : ; do 
  # This single command should run forever, and send events to handleEvents when they come
  /usr/bin/pactl subscribe | handleEvents
done